From b04a218586ab6d0ab854bbbc659fe2ae4a435261 Mon Sep 17 00:00:00 2001 From: DPWilde Date: Sat, 15 Aug 2020 19:03:43 +0100 Subject: [PATCH] Add files via upload --- .gitattributes | 2 + Bandscope.ino | 586 +++++++++ IFsweep.ino | 503 +++++++ LICENSE | 674 ++++++++++ README.md | 28 + SigLo.ino | 290 ++++ SweepLo.ino | 643 +++++++++ cmd.cpp | 2886 ++++++++++++++++++++++++++++++++++++++++ cmd.h | 192 +++ marker.cpp | 304 +++++ marker.h | 125 ++ menu.cpp | 136 ++ menu.h | 67 + my_SA.h | 300 +++++ pE4302.cpp | 232 ++++ pE4302.h | 88 ++ preferences.cpp | 325 +++++ preferences.h | 37 + si4432.cpp | 824 ++++++++++++ si4432.h | 237 ++++ simpleSA.h | 355 +++++ simpleSA.ino | 1913 +++++++++++++++++++++++++++ simpleSA_wifi.cpp | 866 ++++++++++++ simpleSA_wifi.h | 87 ++ ui.cpp | 3196 +++++++++++++++++++++++++++++++++++++++++++++ ui.h | 32 + 26 files changed, 14928 insertions(+) create mode 100644 .gitattributes create mode 100644 Bandscope.ino create mode 100644 IFsweep.ino create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SigLo.ino create mode 100644 SweepLo.ino create mode 100644 cmd.cpp create mode 100644 cmd.h create mode 100644 marker.cpp create mode 100644 marker.h create mode 100644 menu.cpp create mode 100644 menu.h create mode 100644 my_SA.h create mode 100644 pE4302.cpp create mode 100644 pE4302.h create mode 100644 preferences.cpp create mode 100644 preferences.h create mode 100644 si4432.cpp create mode 100644 si4432.h create mode 100644 simpleSA.h create mode 100644 simpleSA.ino create mode 100644 simpleSA_wifi.cpp create mode 100644 simpleSA_wifi.h create mode 100644 ui.cpp create mode 100644 ui.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/Bandscope.ino b/Bandscope.ino new file mode 100644 index 0000000..48acdd5 --- /dev/null +++ b/Bandscope.ino @@ -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" diff --git a/IFsweep.ino b/IFsweep.ino new file mode 100644 index 0000000..ca7607e --- /dev/null +++ b/IFsweep.ino @@ -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 )" + + +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e62ec04 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c3b09a --- /dev/null +++ b/README.md @@ -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 \ No newline at end of file diff --git a/SigLo.ino b/SigLo.ino new file mode 100644 index 0000000..a834584 --- /dev/null +++ b/SigLo.ino @@ -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; + } +} diff --git a/SweepLo.ino b/SweepLo.ino new file mode 100644 index 0000000..17b0e26 --- /dev/null +++ b/SweepLo.ino @@ -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" diff --git a/cmd.cpp b/cmd.cpp new file mode 100644 index 0000000..5491089 --- /dev/null +++ b/cmd.cpp @@ -0,0 +1,2886 @@ +/* + * "Cmd.cpp" was added in Version 2.0 by John Price (WA2FZW) + * + * This file contains the functions to read a command and the associated data + * from the serial input and all the functions necessary to process the + * commands. + * + * The functions to read and interpret the commands were stripped out of the + * "loop" function in the main program file. As it currently exists, the command + * structure has been modified to be more human friendly, and will no longer work + * with Erik's "TintSA.exe" program, which has been replaced with a web page + * interface which is much more flexibile. + * + * The other functions in here all support the main command interpreter and are + * also used by the "ui.cpp" and "TinySA_wifi.cpp" modules. Eventually, we hope + * that those modules will also be able to use the "ProcessCommand" function in + * here in place of much redundant code in those modules. + */ + +#include "Cmd.h" // Our associated header +#include "preferences.h" // For "SAVE" & "RECALL" +#include "Marker.h" // Marker class definition +/* + * These are the objects for the PE4302 and Si4432 modules. They are created in the + * main program file: + */ + +extern PE4302 att; // PE4302 Attenuator object +extern Si4432 rcvr; // Si4432 Receiver object +extern Si4432 xmit; // Si4432 Transmitter object +#ifdef SI_TG_IF_CS +extern Si4432 tg_if; // Si4432 Transmitter object +#endif +#ifdef SI_TG_LO_CS +extern Si4432 tg_lo; // Si4432 Transmitter object +#endif + +/* + * Global variables defined in the main program file: + */ + +extern settings_t setting; // Structure to track & save settings +extern bandpassFilter_t bandpassFilters[]; // RBW Setting data + +extern int bpfCount; // Number of entries in "bandpassFilters" +extern int changedSetting; // Something in "setting" changed + +extern uint16_t tinySA_mode; + +extern uint8_t myData[DISPLAY_POINTS+1]; +extern uint8_t myStorage[DISPLAY_POINTS+1]; + +extern float bandwidth; // The current bandwidth (not * 10) +extern unsigned long delaytime; // In microseconds + +extern unsigned long wiFiTargetTime; +extern uint16_t wiFiPoints; +extern unsigned long websocketInterval; // time in microseconds between websocket polls if no wifi client + +extern int VFO; // TX/RX Si4432 selector + +extern uint16_t steps; // Number of frequency steps in the sweep (not really used) + +extern bool setActualPowerRequested; +extern float actualPower; + +extern uint16_t oldPeakLevel; // Current maximum signal level (was peakLevel) G3ZQC + +int32_t frequency0 = 0; // Used in setting sweep configuration +int32_t frequency1 = 100000000; + +extern uint32_t lastStart; // Last value of the sweep start frequency +extern uint32_t lastStop; // Last value of the sweep end frequency + +extern uint32_t startFreq_IF; // Last value of the IF sweep start frequency +extern uint32_t stopFreq_IF; // Last value of the IF sweep end frequency +extern uint32_t sigFreq_IF; // Last value of the IF sweep end frequency + +extern uint8_t showRSSI; // When true, RSSI readings are displayed +extern int initSweep; // Set by the "RSSI" command to restart the sweep +extern bool paused; + + +extern Marker marker[MARKER_COUNT]; // Array of marker objects + + +/* + * Functions still in the main program file. Maybe some need to move here? + */ + +extern void DisplayInfo (); +extern void DisplayError ( uint8_t severity, const char *line1, const char *line2, const char *line3, const char *line4 ); +extern void RedrawHisto (); +extern void pushSettings (); +extern void pushBandscopeSettings (); +extern void initSweepLow (); +extern void initSigLow (); +extern void initIF_Sweep (); +extern void setMode (uint16_t newMode); +/* + * The "msgTable" provides a way of translating the ASCII message to the internal + * number. Needs to be re-ordered based on expected usage. + */ + + msg_t msgTable[] = + { + { "START", MSG_START }, // Set sweep start frequency + { "STOP", MSG_STOP }, // Set sweep stop frequency + { "CENTER", MSG_CENTER }, // Set sweep center frequency + { "SPAN", MSG_SPAN }, // Set sweep range + { "FOCUS", MSG_FOCUS }, // Set sweep center frequency with narrow bandwidth + { "PREAMP", MSG_PREAMP }, // Set the receiver preamp gain + { "GAIN", MSG_PREAMP }, // Alternate receiver preamp gain + { "DRIVE", MSG_DRIVE }, // Set transmitter (LO) output level + { "FREQ", MSG_VFO_FREQ }, // Set the frequency for the selected VFO + { "ATTEN", MSG_ATTEN }, // Set the PE4301 attenuation + { "HELP", MSG_HELP }, // Display the command menu + { "?", MSG_HELP }, // Display the command menu + { "STEPS", MSG_STEPS }, // Set or get number of sweep points (not used) + { "DELAY", MSG_DELAY }, // Set or get delay time between sweep readings + { "VFO", MSG_VFO }, // Set or get the currently selected VFO + { "RBW", MSG_RBW }, // Set or get the current resolution bandwidth + { "REGDUMP", MSG_REG_DUMP }, // Print register values for the selected VFO + { "RSSI", MSG_RSSI }, // Show RSSI values + { "QUIT", MSG_QUIT }, // Stop RSSI readings + { "REGISTER", MSG_SET_REG }, // Set or get the value of a specific register for the selected VFO + { "SAVE", MSG_SAVE }, // Save scan configuration + { "RECALL", MSG_RECALL }, // Recall a saved scan configuration + { "REF_FREQ", MSG_GPIO2 }, // Set transmitter GPIO2 frequency + { "TUNE", MSG_TUNE }, // Tune the selected Si4431 (VFO) + { "CONFIG_SAVE", MSG_CONFIG }, // Save "config" structure + { "ACTUAL_PWR", MSG_ACT_PWR }, // Calibrate the indicated power level + { "IF_FREQ", MSG_IF_FREQ }, // Change the IF (receiver) frequency + { "TRACES", MSG_TRACES }, // Turn traces on the display on or off + { "GRIDREF", MSG_GRID }, // Set dB value for the top line of the grid + { "SCALE", MSG_SCALE }, // Set dB/horizontal line value for the grid + { "PAUSE", MSG_PAUSE }, // Pause at end of sweep - toggled + { "SALO", MSG_SWEEPLO }, // Sweep low range mode + { "SGLO", MSG_SIGLO }, // Signal generator low range mode + { "IFSWEEP", MSG_IF_SWEEP }, // IF sweep mode + { "BANDSCOPE", MSG_BANDSCOPE }, // BANDSCOPE mode + { "MARKER", MSG_MARKER }, // Set marker status and color + { "SPUR", MSG_SPUR }, // Set Spur reduction on/off + { "WIFITIME", MSG_WIFI_UPDATE }, // Set WiFi Update target time + { "WIFIPTS", MSG_WIFI_POINTS }, // Set WiFi points + { "SKTINT", MSG_WEBSKT_INTERVAL }, // Set WebSocket interval + { "SGRXDRIVE", MSG_SG_RX_DRIVE }, // Set Signal Generator RX Drive level + { "SGLODRIVE", MSG_SG_LO_DRIVE }, // Set Signal Generator LO Drive level + { "TGIFDRIVE", MSG_TG_IF_DRIVE }, // Set Track Generator IF Drive level + { "TGLODRIVE", MSG_TG_LO_DRIVE }, // Set Track Generator LO Drive level + { "IFSIGNAL", MSG_IFSIGNAL }, // IF sweep signal frequency + { "TGOFFSET", MSG_TGOFFSET }, // Offset TG IF from SA IF to reduce pass through + { "", MSG_NONE } // Unrecognized command + }; + +int msgCount = ELEMENTS ( msgTable); + + +/* + * "ShowMenu" displays the command menu on the USB output (Arduino IDE's Serial + * Monitor, or some other terminal emulator program). + */ + +void ShowMenu () +{ + Serial.printf ( "\nTiny Spectrum Analyzer Version %s - User Commands:\n\n", PROGRAM_VERSION ); + Serial.println ( "Sweep Settings:\n" ); + + Serial.print ( " START.........Sweep start frequency, currently: " ); + Serial.println ( FormatFrequency ( setting.ScanStart )); + + Serial.print ( " STOP..........Sweep stop frequency, currently: " ); + Serial.println ( FormatFrequency ( setting.ScanStop )); + + Serial.print ( " CENTER........Sweep center frequency, currently: " ); + Serial.println ( FormatFrequency ( GetSweepCenter() )); + + Serial.print ( " SPAN..........Sweep frequency span, currently: " ); + Serial.println ( FormatFrequency ( setting.ScanStop - setting.ScanStart )); + + Serial.println ( " MARKER........It's complicated; read the documentation!" ); + + Serial.println ( " FOCUS.........Set single frequency mode with narrow bandwidth" ); + + Serial.printf ( " RBW...........Set or get resolution bandwidth (RBW); currently: %.1f KHz\n", bandwidth ); + + Serial.printf ( " ATTEN.........Set or get the attenuator setting; currently: %ddB\n", + setting.Attenuate ); + + Serial.printf ( " SPUR......... Turn Spur Reduction 'ON' or 'OFF'; currently: %s\n", + setting.Spur ? "ON" : "OFF" ); + + Serial.println ( " PAUSE.........Pause or resume the sweep" ); + + Serial.println ( "\nDisplay Options:\n" ); + + Serial.println ( " SALO..........Set to analyse mode low frequency range" ); + Serial.println ( " SGLO..........Set to signal generator mode low frequency range" ); + Serial.println ( " IFSWEEP.......Set to IF Sweep mode to analyse the TinySA SAW filters" ); + Serial.println ( " BANDSCOPE.....Set to BANDSCOPE mode" ); + + Serial.println ( " TRACES........Turn display traces on or off ['GAIN' or 'dB']" ); + + Serial.printf ( " PREAMP/GAIN...Set or get the receiver preamp gain\n" ); + Serial.println ( " See documentation for allowed values" ); + + Serial.printf ( " GRIDREF.......Set the grid reference level; currently: %ddB\n", setting.MaxGrid ); + + Serial.printf ( " SCALE.........Set the dB/horizontal line value; currently: %ddB\n", setting.PowerGrid ); + + Serial.println ( "\nOther Commands:\n" ); + + Serial.print ( " DRIVE.........Local oscillator drive level [0 to 7]; currently: " ); + Serial.println ( setting.Drive ); + + Serial.print ( " SGLODRIVE.....Local oscillator drive level in signal generator mode [0 to 7]; currently: " ); + Serial.println ( sigGenSetting.LO_Drive ); + + Serial.print ( " SGRXDRIVE.....Local oscillator drive level in signal generator mode [0 to 7]; currently: " ); + Serial.println ( sigGenSetting.RX_Drive ); + + Serial.print ( " TGLODRIVE.....Local oscillator drive level in tracking generator mode [0 to 7]; currently: " ); + Serial.println ( trackGenSetting.LO_Drive ); + + Serial.print ( " TGIFDRIVE.....Local oscillator drive level in tracking generator mode [0 to 7]; currently: " ); + Serial.println ( trackGenSetting.IF_Drive ); + + Serial.print ( " TGOFFSET......Tracking generator offset from SA IF; currently: " ); + Serial.println ( trackGenSetting.Offset ); + + Serial.println ( " SAVE .........Save the current scan configuration [0 to 4]" ); + + Serial.println ( " RECALL........Recall a saved scan configuration [0 to 4]" ); + + Serial.println ( " FREQ..........Set or get the frequency for the selected VFO" ); + + Serial.println ( " HELP (or ?)...Show this menu" ); + + Serial.printf ( " STEPS.........Sweep samples; currently: %u\n", steps ); + + Serial.printf ( " DELAY.........Timestep in uS, currently: %uuS\n", delaytime ); + + Serial.print ( " VFO...........Set or get active VFO [R, ( L or T ), I(tg If), G(tG LO)]; currently: " ); + + if ( VFO == RX_4432 ) + Serial.println ( "RX (0)" ); + else if (VFO == TX_4432 ) + Serial.println ( "LO (1)" ); + else if (VFO == TGIF_4432 ) + Serial.println ( "TGIF (2)" ); + else if (VFO == TGLO_4432 ) + Serial.println ( "TGLO (3)" ); + + Serial.println ( "\nDebugging and Troubleshooting Commands:\n" ); + + Serial.printf ( " IF_FREQ.......Set the IF (receiver) frequency [433 to 435MHz]; currently: %s\n", + FormatFrequency ( setting.IF_Freq )); + + Serial.println ( " REGDUMP.......Display a dump of all the registers for the selected VFO" ); + + Serial.println ( " RSSI..........Output RSSI readings until the 'Q' command is entered" ); + Serial.println ( " or one time only ['C' or '1']; the default is once."); + + Serial.println ( " QUIT (or Q)...Terminate RSSI output" ); + + Serial.println ( " REGISTER......Read or write a Si4432 register for the selected VFO" ); + + Serial.println ( " REF_FREQ......Set or get the transmitter GPIO2 reference frequency" ); + + Serial.println ( " TUNE..........Tune the frequency of the selected VFO" ); + + Serial.println ( " ACTUAL_PWR....Calibrate the indicated power level" ); + + Serial.println ( " CONFIG_SAVE...Save the hardware configuration parameters" ); + + Serial.println ( " WiFiTIME......Target web page Chart Update time interval in ms" ); + + Serial.println ( " WiFiPTS.......No of points in each data chunk pushed to the web clients" ); + + Serial.println ( " SKTINT........Interval between check for websocket messages if no client connected" ); + + Serial.println ( " IFSIGNAL......Frequency of signal injected for IF Sweep (External or Ref)" ); + + Serial.println (); +} + + +/* + * "CheckCommand" checks the serial input for any messages. If there is nothing + * to read, it returns "false". + * + * If there is something to be read, the complete line of data is read into a + * buffer. The read loop is set up to recognize either a return or newline character + * as the line terminator and handles the case where both are used (as is possible + * when using the Arduino IDE's Serial Monitor. The function also translates all + * alpha characters to upper case. + * + * Once the command is read, the buffer is passed to "ProcessCommand" which does + * whatever was requested. If the command is successfully executed, the function + * returns "true" and returns "false" if the command was not successful. + */ + +bool CheckCommand () +{ + +char inBuff[80]; // Input buffer +char c = 0; // Just one character +int16_t index = 0; // Index to the buffer + + if ( !Serial.available() ) // Any input? + return false; // Nope! + + while ( Serial.available () ) // While data to read + { + c = toupper ( Serial.read () ); // Get next character + + if (( c == '\r' ) || ( c == '\n' )) // End of the line? + inBuff[index++] = '\0'; // Replace with a null + + else // Not the end of the line + inBuff[index++] = c; // Save the character + + inBuff[index] = '\0'; // Make next character a null + } + + return ParseCommand ( inBuff ); // Process the command and return result +} + + +/* + * "ParseCommand" Translates the ASCII command text into a numerical value based + * on the contents of the "msgTable" and separates the data portion of the message + * (if any) into the "dataBuff". + * + * The process is a bit clever! The user only need to enter wnough characters of the + * command to make it unique! For example, if the user simply entered the command as + * "D", that could be interpreted as meaning "DRIVE" or "DELAY" (based on the contents + * of the "msgTable" as I write this). But entering "DE" or "DR" is enough to distinguish + * between the two. + * + * If the command is not found or is not unique, appropriate error messages are + * diaplayed and the function returns false. + * + * If the command is identified uniquely, the "ProcessCommand" function is invoked + * to complete the processing. + */ + +bool ParseCommand ( char* inBuff ) // inBuff has full message +{ + int messageLength; // Length of entire message + int cmdLength; // Length of command string + uint8_t cmdNumber; // Command translated to a number + + int cmdCount = 0; // Number of commands matching the input + int inputIx = 0; // Input buffer index + int cmdIx = 0; // Command string index + int dataIx = 0; // Data string index + + char cmdString[10]; // Will contain the command string + char dataBuff[20]; // Will contain the data portion of the message + char fullCmd[20]; // Full command from table + + char c; // Just one character + + memset ( cmdString, 0, sizeof ( cmdString )); // Clear the command buffer + memset ( dataBuff, 0, sizeof ( dataBuff )); // Clear the data buffer + + messageLength = strlen ( inBuff ); // Length of input + + if ( messageLength == 0 ) + return false; + + while ( inputIx < messageLength ) + { + c = toupper ( inBuff[inputIx++] ); // Get a character + + if (( c != ' ' ) && ( c != ',' )) // If it's not a space or a comma + cmdString[cmdIx++] = c; // Add to commmand string + + else // If it is a space or comma + break; // On to the data part of the message + } + + while ( inputIx < messageLength ) + dataBuff[dataIx++] = inBuff[inputIx++]; // Copy rest of message to the data buffer + + cmdLength = strlen ( cmdString ); // Length of the command string + cmdNumber = MSG_NONE; // No message found so far + + for ( inputIx = 0; inputIx < msgCount; inputIx++ ) // Search the msgTable + { + if ( strncmp ( cmdString, msgTable[inputIx].Name, cmdLength ) == 0 ) + { + cmdNumber = msgTable[inputIx].ID; // Get translation + strcpy ( fullCmd, msgTable[inputIx].Name ); // And remember full command string + cmdCount++; // Found at least one match + + if ( cmdCount > 1 ) // But did we find more than one? + { + Serial.printf ( "'%s' Is ambiguous!\n", cmdString ); + return false; + } + } + } + + if ( cmdNumber != MSG_NONE ) + { +// Serial.print ( "Command: " ); Serial.print ( fullCmd ); +// Serial.print ( " = " ); Serial.println ( cmdNumber ); +// Serial.print ( "Data: " ); Serial.println ( dataBuff ); + + return ( ProcessCommand ( cmdNumber, dataBuff )); + } + + else + + Serial.printf ( "Command: '%s' not found!\n", cmdString ); + + return false; +} + + +/* + * "ProcessCommand" parses the input buffer appropriately for the command, which is + * (for now) a single letter in the 1st byte of the buffer. + * + * If the command is executed correctly, the function returns a "true" indication + * and returns "false" if some error occurred. + */ + +bool ProcessCommand ( uint8_t command, char* dataBuff ) +{ +uint16_t dataIx = 0; // Data buffer index +uint8_t dataLen; // Length of data buffer + +char tempBuff[40]; // Buffer for formatting responses +int16_t tempIx = 0; // tempBuff index + +char c = 0; // A single character from the input + +int16_t addr; // Si4432 register address + +int16_t tempValue; // Temporary value of something +int32_t tempFreq; // Temporary frequency + +uint32_t freq1; // Several other temporary frequencies +uint32_t freq2; // used in computing the center +uint32_t freq3; // frequency and span range +uint32_t freq4; +int32_t signedFreq; + +int16_t attenuation; // Attenuation setting for the PE4302 + +uint8_t driveLevel[] = { 1, 2, 5, 8, 11, 14, 17, 20 }; // Drive level to dB translation +uint8_t fTranslate[7] = { 30, 15, 10, 4, 3, 2, 1 }; // GPIO2 frequency table + +bool agcOn; // Used by "GetPreampGain" +uint8_t reg69; // Ditto + + dataLen = strlen ( dataBuff ); // Length of the data string + +// Serial.print ( "Command: " ); Serial.println ( command ); +// Serial.print ( "Data: " ); Serial.println ( dataBuff ); + + +/* + * The original code had a plethora of "if" statements to try to figure out what + * command was entered; modified to a "switch" statement in Version 1.7. + */ + + switch ( command ) + { + + +/* + * The marker command is a bit more complicated than most. The format of the command + * is: + * + * MARKER # ACTION + * + * where '#' is the marker number (1 to 4) and 'ACTION' is one of the following: + * + * ENABLE Turns it on + * DISABLE Turns it off + * WHITE Set the color to white + * RED Set the color to red + * BLUE etc. + * GREEN + * YELLOW + * ORANGE + * + * Only the first letter of the 'ACTION' is necessary, so the command "MARKER 2 R" + * would set the color of marker #2 to red. + */ + + case MSG_MARKER: // Set marker status or color + if ( dataLen < 1 ) // Less than 1; no number specified + { + Serial.println ( "No marker number specified" ); + return false; + } + + if ( !isDigit ( dataBuff[0] )) // Should be the marker number + { + Serial.println ( "No marker number specified" ); + return false; + } + + if ( dataLen < 3 ) // Has to be at least 3 characters + { + Serial.println ( "No marker action specified" ); + return false; + } + + tempValue = dataBuff[0] - 0x30 - 1; // Quick atoi conversion + + if ( tempValue >= MARKER_COUNT ) // Range check + { + Serial.printf ( "Illegal marker number: %i\n", tempValue + 1); + return false; + } + + if ( UpdateMarker ( tempValue, dataBuff[2] )) // Update the status + return true; + + else + { + Serial.printf ( "Illegal marker action: %s\n", + &dataBuff[2] ); + return false; + } +/* + * The "START" command sets the scan START frequency. and the "STOP" command + * sets the scan STOP frequency. These are fairly straightforward! + */ + + case MSG_START: // Set the start frequency + if ( dataLen != 0 ) // Frequency specified? + { + tempFreq = ParseFrequency ( dataBuff ); // Then get the new frequency + SetSweepStart ( tempFreq ); // Set it + RedrawHisto(); + //DisplayInfo (); // Restart scan? + } + + Serial.printf ( "Scan start frequency: %s\n", + FormatFrequency ( GetSweepStart () )); + + return true; + + + case MSG_STOP: // Set the stop frequency + if ( dataLen != 0 ) // Frequency specified? + { + tempFreq = ParseFrequency ( dataBuff ); // Get new frequency + SetSweepStop ( tempFreq ); // And set it + RedrawHisto (); + } + + Serial.printf ( "Scan stop frequency: %s\n", + FormatFrequency ( GetSweepStop () )); + + return true; + + +/* + * When the user sets the "CENTER" frequency, we're also going to set the "SPAN". But, + * it's a bit tricky! + * + * The basic assumption is that we will set the "SPAN" equal to the selected "CENTER" + * frequency. But if the "SPAN" limits would cause the "START" frequency to go negative + * or cause the "STOP" frequency to exceed "STOP_MAX", the difference between the + * "CENTER" frequency and whichever limit is exceeded will become 1/2 of the "SPAN". + * + * Of course, setting the "CENTER" frequency at either one of the limits is going to + * result in a zero span. + */ + + case MSG_CENTER: // Set the center frequency + + if ( dataLen != 0 ) // Frequency specified? + { + tempFreq = ParseFrequency ( dataBuff ); // Yes, get new frequency + SetSweepCenter ( tempFreq, WIDE ); // Set it + RedrawHisto (); + } + + Serial.printf ( "Scan center frequency: %s\n", + FormatFrequency ( GetSweepCenter ( ) )); + + return true; + + + case MSG_SPAN: // Set the frequency span + if ( dataLen != 0 ) // Frequency specified? + { + tempFreq = ParseFrequency ( dataBuff ); // Yes, get new frequency + SetSweepSpan ( tempFreq ); // and set it + RedrawHisto (); + } + + Serial.printf ( "Sweep frequency span: %s\n", + FormatFrequency ( GetSweepSpan ( ) )); + + return true; + + + case MSG_FOCUS: // Set single frequency mode + if ( dataLen != 0 ) // Frequency specified? + { + tempFreq = ParseFrequency ( dataBuff ); // Yes, get new frequency + SetSweepCenter ( tempFreq, NARROW ); // Set it + RedrawHisto (); + } + + Serial.printf ( "Scan center frequency: %s\n", + FormatFrequency ( GetSweepCenter () )); + + return true; + +/* + * The "GRID" command sets the decibel value for the top grid line. + * + * If no number is entered, we report the current setting. + */ + + case MSG_GRID: + if ( dataLen != 0 ) // Value specified? + { + tempValue = atoi ( dataBuff ); // Yes, get grid reference value + SetRefLevel ( tempValue ); // Set it + DisplayInfo (); // Re display the scan + } + + Serial.printf ( "Grid reference value: %ddB\n", setting.MaxGrid ); + return true; + + +/* + * The "SCALE" command sets the decibel value spacing for each horizontal line on the grid. + * Unlike the touch-screen equivalent command, here you can set any value you like as opposed + * to chosing from a list of standard settings. + * + * If no number is entered, we report the current setting. + */ + + case MSG_SCALE: + if ( dataLen != 0 ) // Value specified? + { + tempValue = atoi ( dataBuff ); // Yes, get scale setting + SetPowerGrid ( tempValue ); // Set it + DisplayInfo (); // Re display the scan + } + + Serial.printf ( "Grid scale: %ddB/div\n", setting.PowerGrid ); + return true; + + +/* + * The "PREAMP" command sets the receiver Si4432 preamp gain. The way this works is pretty + * strange and is explained in A440, but not very clearly! + * + * There are two ranges; low and high. The low range values are 5dB to 29dB in 3dB steps. + * The high range goes from 25dB to 49dB in 3dB steps. Note there is some overlap! You can + * also specify "AUTO" mode which turns on the AGC in the module. + * + * Rather than go through the hassle of figuring out if the value entered by the user is + * one of the specific values allowed, we will allow the user to enter any value between + * 5dB and 49dB and if it's not one of the legal values, we will use the next lower value. + * + * Note the ranges overlap, so if the requested value is greater than 23dB, we'll just use + * the high range (in other words, we won't use 26dB or 29dB). + */ + + case MSG_PREAMP: + if ( dataLen != 0 ) // Any data? + { + if ( dataBuff[0] == 'A' ) // "AUTO" mode requested? + tempValue = AGC_ON; // Turn on the AGC + + else if ( isDigit ( dataBuff[0] )) // It should be a number + tempValue = atoi ( dataBuff ); // Convert it to a number + + SetPreampGain ( tempValue ); // Set the new value + } // End of if ( dataLen != 0 ) + + tempValue = GetPreampGain ( &agcOn, ®69 ); + + if ( agcOn ) // If AGC is on + Serial.println ( "Preamp AGC is on" ); + else // No AGC, report real gain + Serial.printf ( "Preamp gain is %ddB\n", tempValue ); + + pushSettings (); + + return true; + + +/* + * The "DRIVE" command sets the local oscillator "drive" level. + */ + case MSG_DRIVE: + if ( dataLen != 0 ) // Drive level specified? + { + tempValue = atoi ( dataBuff ); // Yes, convert to a number + + if (( tempValue < MIN_DRIVE ) || ( tempValue > MAX_DRIVE )) + Serial.printf ( "Invalid drive specification: %d\n", tempValue ); + else // Set the transmitter drive level + SetLoDrive ( tempValue ); // "SetLoDrive" saves the settings + } + + Serial.printf ( "Drive: %d (+%udBm)\n", setting.Drive, driveLevel[setting.Drive] ); + + pushSettings (); + + return true; + + +/* + * The "SGLODRIVE" command sets the local oscillator "drive" level in signal generator mode. + */ + case MSG_SG_LO_DRIVE: + if ( dataLen != 0 ) // Drive level specified? + { + tempValue = atoi ( dataBuff ); // Yes, convert to a number + + if (( tempValue < MIN_DRIVE ) || ( tempValue > MAX_DRIVE )) + Serial.printf ( "Invalid drive specification: %d\n", tempValue ); + else // Set the transmitter drive level + SetSGLoDrive ( tempValue ); // "SetSGLoDrive" saves the settings + } + + Serial.printf ( "SigGen LO Drive: %d (+%udBm)\n", sigGenSetting.LO_Drive, driveLevel[sigGenSetting.LO_Drive] ); + + //pushSettings (); + + return true; + +/* + * The "SGLODRIVE" command sets the receiver (IF) oscillator "drive" level in signal generator mode. + */ + case MSG_SG_RX_DRIVE: + if ( dataLen != 0 ) // Drive level specified? + { + tempValue = atoi ( dataBuff ); // Yes, convert to a number + + if (( tempValue < MIN_DRIVE ) || ( tempValue > MAX_DRIVE )) + Serial.printf ( "Invalid drive specification: %d\n", tempValue ); + else // Set the transmitter drive level + SetSGRxDrive ( tempValue ); // "SetTGRxDrive" saves the settings + } + + Serial.printf ( "SigGen RX Drive: %d (+%udBm)\n", sigGenSetting.RX_Drive, driveLevel[sigGenSetting.RX_Drive] ); + + //pushSettings (); + + return true; + + +#ifdef SI_TG_LO_CS +/* + * The "TGLODRIVE" command sets the local oscillator "drive" level in tracking generator mode. + * (only valid if two SI4432 used for the tracking generator) + */ + case MSG_TG_LO_DRIVE: + if ( dataLen != 0 ) // Drive level specified? + { + tempValue = atoi ( dataBuff ); // Yes, convert to a number + + if (( tempValue < MIN_DRIVE ) || ( tempValue > MAX_DRIVE )) + Serial.printf ( "Invalid drive specification: %d\n", tempValue ); + else // Set the transmitter drive level + SetTGLoDrive ( tempValue ); // "SetTGLoDrive" saves the settings + } + + Serial.printf ( "Tracking Generator LO Drive: %d (+%udBm)\n", trackGenSetting.LO_Drive, driveLevel[trackGenSetting.LO_Drive] ); + + //pushSettings (); + + return true; +#endif + + +#ifdef SI_TG_IF_CS +/* + * The "TGRXDRIVE" command sets the receiver (IF) oscillator "drive" level in tracking generator mode. + */ + case MSG_TG_IF_DRIVE: + if ( dataLen != 0 ) // Drive level specified? + { + tempValue = atoi ( dataBuff ); // Yes, convert to a number + + if (( tempValue < MIN_DRIVE ) || ( tempValue > MAX_DRIVE )) + Serial.printf ( "Invalid drive specification: %d\n", tempValue ); + else // Set the transmitter drive level + SetTGIfDrive ( tempValue ); // "SetTGLoDrive" saves the settings + } + + Serial.printf ( "Tracking Generator IF Drive: %d (+%udBm)\n", trackGenSetting.IF_Drive, driveLevel[trackGenSetting.IF_Drive] ); + + //pushSettings (); + + return true; +#endif + + case MSG_SAVE: // Save the scan configuration + if ( dataLen != 0 ) // Location specified? + { + Save ( atoi ( dataBuff )); // Save the settings + return true; + } + + else + { + Serial.println ( "No save location specified!" ); + return false; + } + + + case MSG_RECALL: // Retrieve saved scan configuration + if ( dataLen != 0 ) // Location specified? + { + Recall ( atoi ( dataBuff )); // Save the settings + return true; + } + + else + { + Serial.println ( "No save location specified!" ); + return false; + } + + +/* + * "FREQ" - Get or set the frequency for selected VFO + */ + + case MSG_VFO_FREQ: + if ( dataLen != 0 ) // Frequency specified? + { + freq1 = ParseFrequency ( dataBuff ); // Yes, get new frequency + + if ( VFO == RX_4432 ) // Receiver? + rcvr.SetFrequency ( freq1 ); + + if ( VFO == TX_4432 ) // Transmitter? + xmit.SetFrequency ( freq1 ); +#ifdef SI_TG_IF_CS + if ( VFO == TGIF_4432 ) // Track generator IF? + tg_if.SetFrequency ( freq1 ); +#endif +#ifdef SI_TG_LO_CS + if ( VFO == TGLO_4432 ) // Track generator IF? + tg_lo.SetFrequency ( freq1 ); +#endif + } + + if ( VFO == RX_4432 ) // Receiver? + Serial.printf ( "RX frequency: %s\n", FormatFrequency ( rcvr.GetFrequency() )); + + if ( VFO == TX_4432 ) // Local Oscillator? + Serial.printf( "LO frequency: %s\n", FormatFrequency ( xmit.GetFrequency() )); +#ifdef SI_TG_IF_CS + if ( VFO == TGIF_4432 ) // Local Oscillator? + Serial.printf( "TG IF frequency: %s\n", FormatFrequency ( tg_if.GetFrequency() )); +#endif +#ifdef SI_TG_LO_CS + if ( VFO == TGLO_4432 ) // Local Oscillator? + Serial.printf( "TG LO frequency: %s\n", FormatFrequency ( tg_lo.GetFrequency() )); +#endif + return true; + + +/* + * The "ATTEN" command sets the PE4302 attenuation + */ + + case MSG_ATTEN: // Set attenuation + if ( dataLen != 0 ) // Anything entered? + { + attenuation = atoi ( dataBuff ); // Get the value + + if (( attenuation < 0 ) + || ( attenuation > PE4302_MAX )) // Range check + { + Serial.printf ( "Illegal attenuator setting: %ddB\n", attenuation ); + return false; + } + + SetAttenuation ( attenuation ); + } + + Serial.printf ( "Attenuator setting: %ddB\n", setting.Attenuate ); + + pushSettings (); + + return true; + + + case MSG_HELP: // "HELP" or "?" + ShowMenu (); + return true; + + + case MSG_STEPS: // Set number of scan points + if ( dataLen != 0 ) // Anything entered? + steps = atoi ( &dataBuff[dataIx] ); + + Serial.print ( "Steps: " ); + Serial.println ( steps ); + return true; + + +/* + * The "DELAY" command sets the timestep in uS. + */ + + case MSG_DELAY: // Set the timestep in uS, currently 2000 + if ( dataLen != 0 ) // Anything entered? + { + delaytime = atol ( dataBuff ); // Yes, set new delay time + DisplayInfo (); + } + + Serial.printf ( "Time per scan step = %uuS\n", delaytime ); + return true; + + +/* + * The "VFO" command sets the active VFO + */ + + case MSG_VFO: + if ( dataLen != 0 ) // Anything entered? + { + if ( dataBuff[0] == 'R' ) // Receiver? + VFO = RX_4432; + + else if (( dataBuff[0] == 'L' ) + || ( dataBuff[0] == 'T' )) // Transmitter? + VFO = TX_4432; + +#ifdef SI_TG_IF_CS + else if ( dataBuff[0] == 'I' ) // track gen IF + VFO = TGIF_4432; +#endif +#ifdef SI_TG_LO_CS + else if ( dataBuff[0] == 'G' ) // track gen LO + VFO = TGLO_4432; +#endif + else // Illegal entry + { + VFO = RX_4432; // No, default to receiver + Serial.println ( "Illegal VFO specification! "); + } + } + + Serial.print ( "Selected VFO is: " ); // Confirm setting + + switch ( VFO ) + { + case RX_4432: // Receiver + Serial.printf ( " Receiver (%u)\n", VFO ); + break; + case TX_4432: // Transmitter (LO) + Serial.printf ( " Local Oscillator (%u)\n", VFO ); + break; + case TGIF_4432: // Transmitter (LO) + Serial.printf ( " Tracking Generator IF (%u)\n", VFO ); + break; + case TGLO_4432: // Transmitter (LO) + Serial.printf ( " Tracking Generator LO (%u)\n", VFO ); + break; + + } + return true; + + +/* + * The "RBW" command sets the resolution bandwidth (RBW). + * + * Entering "RBW A" (or "RBW AUTO") puts the RBW into automatic mode. + */ + + case MSG_RBW: + if ( dataLen != 0 ) // Anything entered? + { + if ( dataBuff[0] == 'A' ) // Auto RBW requested? + bandwidth = 0; // Set bandwidth to '0' + + else if ( isDigit ( dataBuff[0] )) // Was a number entered? + { + bandwidth = atof ( dataBuff ); // Yes, then get entered value + + if ( bandwidth < 2.7 ) + bandwidth = 2.7; + } + +// Serial.print ( "RBW: " ); Serial.println ( bandwidth ); + +// setting.Bandwidth10 = (int) ( bandwidth * 10 ); // Here also + + SetRBW ( (int) ( bandwidth * 10 )); + } + + Serial.print ( "RBW = " ); + + if ( setting.Bandwidth10 == 0 ) + Serial.printf ( "AUTO (%.1fHKz)\n", bandwidth ); + + else + Serial.printf ( "%.1fHKz\n", bandwidth ); + + return true; // End of 'W' command + + + case MSG_TRACES: // Only works on 'GAIN' trace for now + if ( dataLen != 0 ) // Anything entered? + { + if ( dataBuff[0] == 'G' ) // Gain trace requested? + { + setting.ShowGain = !setting.ShowGain; // Toggle the setting + WriteSettings (); // Save setting + return true; // Processed successfully + } + + if ( dataBuff[0] == 'D' ); // Manin scan requested? + { + setting.ShowSweep = !setting.ShowSweep; // Toggle the setting + WriteSettings (); // Save setting + return true; // Processed successfully + } + } + + else // Nothing specified + { + Serial.println ( "No 'TRACES' item specified!" ); + return false; // Unsuccessful + } + + + case MSG_IF_FREQ: + if ( dataLen != 0 ) // Anything entered? + { + if ( dataBuff[0] == '+' ) // User wants to add an increment? + { + strcpy ( tempBuff, &dataBuff[1] ); // Copy the number to "tempBuff" + tempValue = 1; // We'll multiply by '+1' + } + + else if ( dataBuff[0] == '-' ) // User wants to add a decrement? + { + strcpy ( tempBuff, &dataBuff[1] ); // Copy the number to "tempBuff" + tempValue = -1; // We'll multiply by '1' + } + + else // No sign entered + { + strcpy ( tempBuff, dataBuff ); // Copy the number to "tempBuff" + tempValue = 0; // Indicate full frequency in the buffer + } + + tempFreq = ParseFrequency ( tempBuff ); // Convert frequency string to a number + + if ( tempValue != 0 ) // Increment or decrement? + tempFreq = ( tempFreq * tempValue ) + setting.IF_Freq; + + if ( !SetIFFrequency ( tempFreq )) // Returns result of range check + Serial.println ( "Requested IF frequency exceeds limits; not changed!" ); + } + + Serial.printf ( "IF Frequency set to: %s\n", FormatFrequency ( setting.IF_Freq )); + return true; + + +/* + * "REGDUMP" Print the registers for selected VFO + */ + + case MSG_REG_DUMP: + if ( VFO == RX_4432 ) // Receiver? + { + Serial.println ( "\nReceiver registers:\n" ); + rcvr.PrintRegs(); + } + + if ( VFO == TX_4432 ) // Transmitter? + { + Serial.println ( "\nTransmitter (LO) registers:\n" ); + xmit.PrintRegs(); + } +#ifdef SI_TG_IF_CS + if ( VFO == TGIF_4432 ) // Track generator IF + { + Serial.println ( "\nTracking Generator IF registers:\n" ); + tg_if.PrintRegs(); + } +#endif +#ifdef SI_TG_LO_CS + if ( VFO == TGLO_4432 ) // Track generator Lo + { + Serial.println ( "\nTracking Generator LO registers:\n" ); + tg_lo.PrintRegs(); + } +#endif + + + return true; + + +/* + * The "RSSI" and "QUIT" commands turn the displaying of the RSSI (Receiver Signal + * Strength Indicator) readings in the scan. + * + * Notice "showRSSI" gets set to '2'. I tried making it an option to only show one + * pass through the sweep, but somehow, the output starts mid-sweep. Until I figure + * that out, continuois is the onlt option. The hooks for that are still in the + * sweep loop. + */ + + case MSG_RSSI: + if ( dataLen != 0 ) // Anything entered? + { + if ( dataBuff[0] == 'C' ) // Continuous reporting requested? + showRSSI = 2; // Yes + + else if ( dataBuff[0] == '1' ) // Or just one time? + showRSSI = 1; // Set it + } + + else // No specification + showRSSI = 1; // Then the default is one time + + initSweep = true; // Restart the sweep + return true; + + + case MSG_QUIT: + showRSSI = 0; // Turn RSSI display off + return true; + + +/* + * The "REGISTER" command can read or write any of the Si4432 registers; it's pretty clever! + * + * The register designation and (optional) data to be written are in HEX. If you enter + * something like "REGISTER 2F", the response will be the contents of the specified register + * (in this case, "2F"). + * + * If you enter something like "REGISTER 69 2", it will write 0x02 into register 0x69. + * + * Note, the current setting of "VFO" ("VFO" command) must be either the transmitter + * or receiver modules. + */ + + case MSG_SET_REG: + tempBuff[0] = 0; // Start with null terminator + tempIx = 0; // And zero out the index; + + if (( VFO != RX_4432 ) && ( VFO != TX_4432 ) && ( VFO != TGIF_4432 ) && ( VFO != TGLO_4432 ) ) + { + Serial.print ( "Illegal Si4432 module specified; 'VFO' = " ); + Serial.println ( VFO ); + return false; + } + + if ( dataLen == 0 ) // Any data? + { + Serial.println ( "No register specified!" ); + return false; + } + + +/* + * Ok, there is some data there so let's extract the register specification. + */ + + while ( dataIx < dataLen ) + { + c = dataBuff[dataIx++]; // Next character from input + + if ((( c != ' ' ) && ( c != ',' )) && ( isHex ( c ))) + { + tempBuff[tempIx++] = c; // Copy to temporary buffer + tempBuff[tempIx] = 0; // Add null terminator + } + + else if (( c != ' ' ) || ( c != ',' )) // If it's a space or a comma + break; // On to the next step + + if ( !isHex ( c )) // If not a valid hex digit + { + Serial.printf ( "'%c' is not a hexadecimal digit!\n", c ); + return false; + } + } + + addr = xtoi ( tempBuff ) & 0xFF; // Convert buffer to address + + tempIx = 0; // Start over for data if any + tempBuff[0] = 0; // Null terminate the buffer + + +/* + * If we got this far, we have a register address. Now we need to determine whether + * or not the user also specified a value to set in that register or if they just + * want a readout of what's in the register. + */ + + while ( dataIx < dataLen ) + { + c = dataBuff[dataIx++]; // Next character from input + + if ( !isHex ( c )) // Check for non hexadecimal digit + { + Serial.printf ( "'%c' is not a hexadecimal digit!\n", c ); + return false; + } + + tempBuff[tempIx++] = c; // Copy to other buffer + tempBuff[tempIx] = 0; // Null terminator + } + + if ( strlen ( tempBuff ) > 0 ) // If something in the buffer + { + tempValue = xtoi ( tempBuff ) & 0xFF; // Convert it to a hex number + + if ( VFO == RX_4432 ) // Receiver? + rcvr.WriteByte ( addr, tempValue ); // Send data to the device + + if ( VFO == TX_4432 ) // Transmitter? + xmit.WriteByte ( addr, tempValue ); // Send data to the device + +#ifdef SI_TG_IF_CS + if ( VFO == TGIF_4432 ) // Track gen IF + tg_if.WriteByte ( addr, tempValue ); // Send data to the device +#endif +#ifdef SI_TG_LO_CS + if ( VFO == TGLO_4432 ) // Track gen LO + tg_lo.WriteByte ( addr, tempValue ); // Send data to the device +#endif +} + + +/* + * So far, so good! When we get here, either the user just wanted to see what's in + * the register or we have already changed it. Now just report the contents of the + * selected register. + */ + + if ( VFO == RX_4432 ) // Receiver? + Serial.printf ( "RX Reg[0x%02X] = 0x%02X\n", addr, rcvr.ReadByte ( addr )); + + else if ( VFO == TX_4432 ) // Transmitter? + Serial.printf ( "TX Reg[0x%02X] = 0x%02X\n", addr, xmit.ReadByte ( addr )); +#ifdef SI_TG_IF_CS + else if ( VFO == TGIF_4432 ) // Transmitter? + Serial.printf ( "TGIF Reg[0x%02X] = 0x%02X\n", addr, tg_if.ReadByte ( addr )); +#endif +#ifdef SI_TG_LO_CS + else if ( VFO == TGLO_4432 ) // Transmitter? + Serial.printf ( "TGLO Reg[0x%02X] = 0x%02X\n", addr, tg_lo.ReadByte ( addr )); +#endif + + return true; + + +/* + * The "TUNE" comand allows the use to specify the crystal load capacitance for the + * selected VFO. This teaks the frequency. See A440 and the TinySA documentation for + * info on how it really works. + */ + + case MSG_TUNE: + if ( dataLen != 0 ) // Value specified? + { + tempValue = xtoi ( dataBuff ); // Yes, get new frequency (in hex) + + if ( VFO == RX_4432 ) // Receiver? + { + rcvr.Tune ( tempValue ); // Tune it + config.RX_capacitance = tempValue; // And save it + } + + if ( VFO == TX_4432 ) // Transmitter? + { + xmit.Tune ( tempValue ); // Tune it + config.TX_capacitance = tempValue; // And save it + } +#ifdef SI_TG_IF_CS + if ( VFO == TGIF_4432 ) // Trackgen IF + { + tg_if.Tune ( tempValue ); // Tune it + config.tgIF_capacitance = tempValue; // And save it + } +#endif +#ifdef SI_TG_LO_CS + if ( VFO == TGLO_4432 ) // Track Gen LO + { + tg_lo.Tune ( tempValue ); // Tune it + config.tgLO_capacitance = tempValue; // And save it + } +#endif + } + + if ( VFO == RX_4432 ) // Receiver? + { + tempValue = rcvr.ReadByte ( REG_COLC ); // Get current setting + Serial.printf ( "RX capacitance: 0x%2X\n", tempValue ); + } + + if ( VFO == TX_4432 ) // Local Oscillator? + { + tempValue = xmit.ReadByte ( REG_COLC ); // Get current setting + Serial.printf ( "TX capacitance: 0x%2X\n", tempValue ); + } + +#ifdef SI_TG_IF_CS + if ( VFO == TGIF_4432 ) // Tracking gen IF + { + tempValue = tg_if.ReadByte ( REG_COLC ); // Get current setting + Serial.printf ( "TGIF capacitance: 0x%2X\n", tempValue ); + } +#endif +#ifdef SI_TG_LO_CS + if ( VFO == TGLO_4432 ) // Tracking gen LO + { + tempValue = tg_lo.ReadByte ( REG_COLC ); // Get current setting + Serial.printf ( "TGLO capacitance: 0x%2X\n", tempValue ); + } +#endif + + return true; + + +/* + * The "REF_FREQ" ("MSG_GPIO2") command sets or gets the current frequency setting for + * the GPIO2 calibration frequency for the transmitter Si4432. + * + * The legal values are: + * + * 30MHz, 15MHz, 10MHz, 4MHz, 3MHz, 2MHz, 1MHz + * + * WA2FZW - For whatever reason, when a value is entered and the register is set, the + * readback of register "REG_MCOC" is correct, but when no data is entered the readback + * is always zero. Needs further investigation! + */ + + case MSG_GPIO2: + if ( dataLen != 0 ) // Value specified? + { + if ( dataBuff[0] == 'O' ) // Turn it off? + tempIx = 7; // This will do it! + else + { + tempValue = atoi ( dataBuff ); // Yes, get new frequency + + for ( tempIx = 0; tempIx < 7; tempIx++ ) // Loop through the table + if ( tempValue == fTranslate[tempIx] ) // A match? + break; // Get out of the loop + + if ( tempIx > 6 ) // Illegal entry + { + Serial.printf ( "'%u' is an illegal GPIO2 frequency setting!\n", tempValue ); + return false; + } + } + SetRefOutput ( tempIx ); // Set the frequency + } + + tempValue = xmit.ReadByte ( REG_GPIO2 ); // Read the GPIO2 register + + if ( tempValue == 0x1F ) + Serial.println ( "Transmitter GPIO2 reference frequency is off!" ); + + else + { + tempValue = xmit.ReadByte ( REG_MCOC ); // Read the clock register + Serial.printf ( "Transmitter GPIO2 reference frequency: %uMHz\n", fTranslate[tempValue] ); + } + + return true; + + + case MSG_ACT_PWR: // Calibrate the indicated power level + if ( dataLen != 0 ) // Value specified? + { + tempValue = atoi ( dataBuff ); // Yes, get new power reading + RequestSetPowerLevel ( tempValue ); + Serial.printf ( "Indicated power set to: %d\n", tempValue ); + return true; + } + + else + { + Serial.println ( "No indicated power specified!" ); + return false; + } + + case MSG_WIFI_UPDATE: // Set WiFi update target time + if ( dataLen != 0 ) // Value specified? + { + tempValue = atoi ( dataBuff ); // Yes, get new value in milliSeconds + // check valid + if ( (tempValue > 10) && (tempValue < 2000) ) + { + wiFiTargetTime = tempValue * 1000; // convert to microSeconds + Serial.printf ( "Target wifi Update time set to: %d\n", tempValue ); + return true; + } + else + { + Serial.println("Invalid - must be between 10ms and 2000ms"); + return false; + } + } + + else + { + Serial.printf ( "WiFi update target time is %ums\n", wiFiTargetTime/1000 ); + return false; + } + + + case MSG_WEBSKT_INTERVAL: // Set WiFi websocket interval time + if ( dataLen != 0 ) // Value specified? + { + tempValue = atoi ( dataBuff ); // Yes, get new value in microSeconds + // check valid + if ( (tempValue > 50) && (tempValue < 10000) ) + { + websocketInterval = tempValue; + Serial.printf ( "websocket poll interval set to: %uus\n", tempValue ); + return true; + } + else + { + Serial.println("Invalid - must be between 50us and 10,000us"); + return false; + } + } + + else + { + Serial.printf ( "Websocket poll interval is %uus\n", websocketInterval ); + return false; + } + + + case MSG_WIFI_POINTS: // Set WiFi chunk size + if ( dataLen != 0 ) // Value specified? + { + tempValue = atoi ( dataBuff ); // Yes, get new value + // check valid + if ( (tempValue > 10) && (tempValue < DISPLAY_POINTS * OVERLAP) ) + { + wiFiPoints = tempValue; + Serial.printf ( "Chunk size set to: %u\n", tempValue ); + return true; + } + else + { + Serial.printf("Invalid - must be between 10 and %u\n", DISPLAY_POINTS * OVERLAP); + return false; + } + } + + else + { + Serial.printf ( "Chunk size is %u\n", wiFiPoints ); + return false; + } + + + + case MSG_CONFIG: // Save configuration settings + WriteConfig (); + return true; + + case MSG_PAUSE: // Pause Scan (toggle) + paused = ! paused; + + if (paused) + Serial.println ( "Sweep paused" ); + + else + Serial.println ( "Sweep resumed" ); + + return true; + + case MSG_SWEEPLO: + setMode(SA_LOW_RANGE); + return true; + + case MSG_SIGLO: + setMode(SIG_GEN_LOW_RANGE); + return true; + + case MSG_BANDSCOPE: + setMode(BANDSCOPE); + return true; + + case MSG_IFSWEEP: + setMode(IF_SWEEP); + return true; + + case MSG_TGOFFSET: + if ( dataLen != 0 ) // Frequency specified? + { + signedFreq = ParseSignedFrequency ( dataBuff ); // Yes, get new frequency + SetTGOffset (signedFreq); + } + Serial.printf ( "Tracking Generator IF offset: %i\n", trackGenSetting.Offset ); // sort out signed version of format frequency!!!!!!!!!!!!!!!!!!! + return true; + + + +/* + * "FREQ" - Get or set the frequency for IF sweep injected signal + */ + + case MSG_IFSIGNAL: + if ( dataLen != 0 ) // Frequency specified? + { + freq1 = ParseFrequency ( dataBuff ); // Yes, get new frequency + SetIFsweepSigFreq (freq1); + } + Serial.printf ( "If Signal frequency: %s\n", FormatFrequency ( GetIFsweepSigFreq() )); + return true; + + + case MSG_SPUR: + if ( dataLen != 0 ) // Anything entered? + { +// if ( dataBuff[0] == '1' ) // Turn Spur reduction on + if ( strcmp ( dataBuff, "ON" ) == 0 ) + { + SetSpur (1); + return true; // Processed successfully + } + + if ( strcmp ( dataBuff, "OFF" ) == 0 ) +// if ( dataBuff[0] == '0' ); // Turn Spur reduction off + { + SetSpur (0); + return true; // Processed successfully + } + + Serial.println( "Invalid argument for Spur command - use 1(on) or 0(off)" ); + return false; + } + + else // Nothing specified + { + Serial.printf ( "Spur reduction: %s\n", setting.Spur ? "ON" : "OFF" ); + return false; + } + + default: + return false; + } // End of "switch" +} // End of "ProcessCommand" + + +/* + * "ParseFrequency" expects a frequency in the "freqString". The format of the string + * is designed to be consistent with the way frequencies are entered using the touch + * screen menus. That is one can enter something like "100M" (or "100m") to specify + * 100MHz or "100k" (or "100K") to specify 100KHz. if you want to enter the + * value in pure Hertz, you can use commas (or dots) for clarity; for example, + * "100,000,000" would specify 100MHz. + */ + +uint32_t ParseFrequency ( char* freqString ) +{ +uint32_t freq = 0; // Used to build the frequency +uint8_t index = 0; // Input buffer index +uint8_t digit; // Next digit + + for ( index = 0; index < strlen ( freqString ); index++ ) + { + digit = freqString[index]; // Get a digit or something else + + if ( isDigit ( digit )) // If it is a number + freq = ( freq * 10 ) + ( digit - 0x30 ); // Cheap atoi conversion + + else if ( digit == 'M' ) + { + freq *= 1000000; // Make it MHz + return freq; // And send it back + } + + else if ( digit == 'K' ) + { + freq *= 1000; // Make it KHz + return freq; // And send it back + } + } + return freq; +} + + +int32_t ParseSignedFrequency ( char* freqString ) +{ +int32_t freq = 0; // Used to build the frequency +uint8_t index = 0; // Input buffer index +uint8_t digit; // Next digit +int32_t negative = 1; + + for ( index = 0; index < strlen ( freqString ); index++ ) + { + digit = freqString[index]; // Get a digit or something else + + if ( isDigit ( digit )) // If it is a number + freq = ( freq * 10 ) + ( digit - 0x30 ); // Cheap atoi conversion + + else if ( digit == 'M' ) + { + freq *= 1000000; // Make it MHz + return freq * negative; // And send it back + } + + else if ( digit == 'K' ) + { + freq *= 1000; // Make it KHz + return freq * negative; // And send it back + } + + else if ( digit == '-' ) + { + negative = -1; + } + } + return freq * negative; +} + +/* + * "FormatFrequency" breaks down the frequency into a format of "nnn,nnn,nnn" in the + * frequency buffer. + */ + +char* FormatFrequency ( uint32_t freq ) +{ + +uint32_t MHz; // MHz part of frequency +uint32_t KHz; // KHz part of frequency +uint32_t remainder; // Leftover pieces +static char freqBuff[20] = {0}; // The formatted frequency + + MHz = freq / 1000000; // Isolate MHz + remainder = freq % 1000000; // The remainder of the frequency + + KHz = remainder / 1000; // Isolate KHz + remainder = remainder % 1000; // Low order 3 digits + + if ( freq >= 1000000 ) + sprintf ( freqBuff, "%lu,%03lu,%03lu Hz", MHz, KHz, remainder ); + + else if ( freq >= 1000 ) + sprintf ( freqBuff, "%lu,%03lu Hz", KHz, remainder ); + + else + sprintf ( freqBuff, "%lu Hz", remainder ); + + return freqBuff; +} + + + +/* + * "FormatSignedFrequency" breaks down the frequency into a format of "nnn,nnn,nnn" in the + * frequency buffer. + */ + +char* FormatSignedFrequency ( int32_t freq ) +{ + +uint32_t uFreq; // turned positive +uint32_t MHz; // MHz part of frequency +uint32_t KHz; // KHz part of frequency +uint32_t remainder; // Leftover pieces +static char freqBuff[20] = {0}; // The formatted frequency +static char firstChar = ' '; + + if (freq < 0) + uFreq = -freq; + else + uFreq = freq; + + if (freq < 0) + firstChar = '-'; + + MHz = uFreq / 1000000; // Isolate MHz + remainder = uFreq % 1000000; // The remainder of the frequency + + KHz = remainder / 1000; // Isolate KHz + remainder = remainder % 1000; // Low order 3 digits + + if ( uFreq >= 1000000 ) + sprintf ( freqBuff, "%c%lu,%03lu,%03lu Hz", firstChar, MHz, KHz, remainder ); + + else if ( uFreq >= 1000 ) + sprintf ( freqBuff, "%c%lu,%03lu Hz", firstChar, KHz, remainder ); + + else + sprintf ( freqBuff, "%c%lu Hz", firstChar, remainder ); + + return freqBuff; +} + + + +/* + * "DisplayFrequency" breaks down the frequency into a format of "nnn,nnn,nnn" in the + * frequency buffer, with leading zeros and no unit + */ + +char* DisplayFrequency ( uint32_t freq ) +{ + +uint32_t MHz; // MHz part of frequency +uint32_t KHz; // KHz part of frequency +uint32_t remainder; // Leftover pieces +static char freqBuff[20] = {0}; // The formatted frequency + + MHz = freq / 1000000; // Isolate MHz + remainder = freq % 1000000; // The remainder of the frequency + + KHz = remainder / 1000; // Isolate KHz + remainder = remainder % 1000; // Low order 3 digits + + sprintf ( freqBuff, "%03lu,%03lu,%03lu", MHz, KHz, remainder ); + + return freqBuff; +} + +/* + * "xtoi()" works similar to "atoi()" except the string is assumed to be hex numbers. It will + * convert characters in the string as long as the next character in the string is a valid hex + * digit. In other words it stops converting if it sees a null or a ';', or any thing else that + * is not a valid hex digit. The only exception is the character "X" (or "x"), which is ignored. + * This allows it to correctly convert a string in the "0xNN..." format. + */ + +uint16_t xtoi ( char* hexString ) +{ +int ix; // Loop index +char c; // Single character +uint16_t answer = 0; // The answer + + for ( ix = 0; ix < strlen ( hexString ); ix++ ) + { + c = toupper ( hexString[ix] ); // Get next character + + if ( c == 'X' ) + continue; + + if ( isdigit ( c )) // Normal base 10 digit? + answer = ( answer * 16 ) + ( c - '0' ); // shift answer and add new number + + else if ( c >= 'A' && c <= 'F' ) // Valid hex digit? + answer = ( answer * 16 ) + ( c - 'A' + 10 ); // Shift answer and add new number + + else + break; + } + + return answer; +} + + +/* + * "isHex" returns true if the character is a valid hexadecimal digit or the letter 'X' + * (or 'x') as used in formatting a hexadecimal number as "0xNN". + */ + + bool isHex ( char c ) + { + char myC = toupper ( c ); // Make uppercase + + if (( myC >= '0' ) && ( myC <= '9' )) // standard digits + return true; + + if (( myC >= 'A' ) && ( myC <= 'F' )) // More digits + return true; + + if ( myC == 'X' ) // Special hex formatting + return true; + + return false; // If none of the above + } + + + +/* + * "UpdateMarker" changes the enabled/disabled status of a marker or its color. + * The "mkr" argument is the index to the marker, not its number. + * + * The "action" argument is (for now) a single character from the following list: + * + * [E]NABLE Turn the marker on + * [D]ISABLE Turn the marker off + * [W]HITE Set the color to white + * [R]ED Set the color to red + * [B]LUE etc. + * [G]REEN + * [Y]ELLOW + * [O]RANGE + */ + +bool UpdateMarker ( uint8_t mkr, char action ) +{ +uint8_t oldMkrStatus; + + oldMkrStatus = marker[mkr].Status (); // Remember marker'scurrent status + + switch ( action ) // What are we supposed to change + { + case 'E': // Enable the marker + marker[mkr].Enable (); + break; // Return success + + case 'D': // Disable the marker + marker[mkr].Disable (); + break; // Return success + + case 'W': // Set color to white + marker[mkr].Color ( WHITE ); + break; // Return success + + case 'R': // Set color to red + marker[mkr].Color ( RED ); + break; // Return success + + case 'B': // Set color to blue + marker[mkr].Color ( BLUE ); + break; // Return success + + case 'G': // Set color to green + marker[mkr].Color ( GREEN ); + break; // Return success + + case 'Y': // Set color to yellow + marker[mkr].Color ( YELLOW ); + break; // Return success + + case 'O': // Set color to orange + marker[mkr].Color ( ORANGE ); + break; // Return success + + default: // If none of the above + return false; // Illegal action/color + } + + +/* + * If we get here, the marker number and action were good. Now see if something + * actually changes and if so, update and save the "setting" structure. + */ + + if ( oldMkrStatus != marker[mkr].Status () ) + { + setting.MkrStatus[mkr] = marker[mkr].Status (); + WriteSettings (); + } + + return true; // Marker command processed ok +} + + + +/* + * "SetPreampGain" sets the preamp gain, which was added to the "setting" structure in + * Version 2.5. "GetPreampGain" reads it from the Si4432 and reports the current setting. + */ + +void SetPreampGain ( uint8_t gain ) +{ +uint8_t oldGain = setting.PreampGain; // Remember current setting + +// Serial.printf ( "Cmd.cpp - SetPreampGain %i \n", gain ); + + if (( gain == 0 ) || ( gain == 0x60 )) // AGC on request? + gain = 0x60; // That will be the setting + + else if ( gain < 25 ) // If < 25 use low range + { + gain = ( gain - 5 ) / 3; // Value for the register + gain &= PGAGAIN; // Just the low 4 bits + } + + else if ( gain >= 25 ) // If we found it above + { + gain = ( gain - 25 ) / 3; // Value for the register + gain &= PGAGAIN; // Just the low 4 bits + gain |= LNAGAIN; // Plus the high range bit + } + + setting.PreampGain = gain; // Set new value in the "setting" structure + rcvr.SetPreampGain ( gain ); // and in the receiver module + + if ( oldGain != setting.PreampGain ) // Did it actually change? + { + changedSetting = true; // Yes, need to update the display + WriteSettings (); // and save the "setting" structure + } +} + +uint8_t GetPreampGain ( bool* agc, uint8_t* reg ) +{ + uint8_t Reg69 = rcvr.GetPreampGain (); // Read the register + int lnaGain = 5; // Assume low gain + + if ( Reg69 & LNAGAIN ) // If LNA is turned on + lnaGain = 25; // Add 25 dB to total gain + + int pgaGain = ( Reg69 & PGAGAIN ) * 3; // 3dB per bit + + *agc = (bool) ( Reg69 & AGCEN ); // Set true if AGC is enabled + *reg = Reg69 & ( PGAGAIN | LNAGAIN ); + + return lnaGain + pgaGain; // Total gain +} + + +/* +* "SetRefOutput" - Sets the GPIO2 frequency for the LO Si4432 module +*/ + +void SetRefOutput ( int freq ) +{ +int oldFreq = setting.ReferenceOut; + + setting.ReferenceOut = freq; + xmit.SetPowerReference ( freq ); + + if ( oldFreq != setting.ReferenceOut ) + { + changedSetting = true; + WriteSettings (); + } +} + + +/* + * "SetRefLevel" - Sets the decibel level for the top line of the graph. + */ + +void SetRefLevel ( int ref ) +{ +int oldMin = setting.MinGrid; +int oldMax = setting.MaxGrid; + + setting.MaxGrid = ref; + setting.MinGrid = ref - Y_GRID * setting.PowerGrid; + + if (( oldMin != setting.MinGrid ) || ( oldMax != setting.MaxGrid )) + { + changedSetting = true; + WriteSettings (); + } +} + + +/* + * "SetGenerate" - Puts the unit into Signal Generator Mode + * Will be superceded by changing setting.mode + */ + +void SetGenerate ( int8_t g ) +{ +int oldG = setting.Generate; + + setting.Generate = g; + if (g) { + tinySA_mode = SIG_GEN_LOW_RANGE; + } else { + tinySA_mode = SA_LOW_RANGE; + } + + if ( oldG != setting.Generate ) + { + changedSetting = true; + WriteSettings (); + } +} + + +/* + * "SetTracking" - sets the Tracking Generator Mode + */ + +void SetTracking ( int8_t m ) +{ +int oldM = trackGenSetting.Mode; + + trackGenSetting.Mode = m; + + if ( oldM != trackGenSetting.Mode ) + { + changedSetting = true; + WriteTrackGenSettings (); + } +} + + +/* + * "SetPowerGrid" - Sets the dB/vertical divison on the grid + */ + +void SetPowerGrid ( int g ) +{ +int oldGrid = setting.PowerGrid; + + setting.PowerGrid = g; + setting.MinGrid = setting.MaxGrid - Y_GRID * g; + + if ( oldGrid != setting.PowerGrid ) + { + changedSetting = true; + WriteSettings (); + } +} + + +/* + * Set the IF frequency - M0WID addition + */ + +bool SetIFFrequency ( int32_t freq ) +{ +int32_t oldFreq = setting.IF_Freq; + +// Serial.print ( "SetIF: freq = " ); Serial.println ( freq ); + + if (( freq < MIN_IF_FREQ ) || ( freq > MAX_IF_FREQ )) // If out of limits + return false; // Don't change it + +// Serial.println ( "SetIF: Past range check" ); + + setting.IF_Freq = freq; + + if ( oldFreq != setting.IF_Freq ) + { + changedSetting = true; + WriteSettings (); + } + + return true; // Frequency was good +} + + +/* + * "SetAttenuation" - Modified by WA2FZW: + * + * "setting.Attenuate" is now stored as a positive number as displaying + * an attenuation of "-nn" (as was the case in the original software) + * would imply a gain value. All other places where it is used have been + * modified accordingly. + * + * Note, we don't actually set the attenuation in the PE4302 as that is done at + * the start of each sweep cycle. + */ + +void SetAttenuation ( int a ) +{ +int oldAtten = setting.Attenuate; + + setting.Attenuate = a; // Put value in the setting structure + + if ( oldAtten != setting.Attenuate ) + { + changedSetting = true; + WriteSettings (); + } +} + + +/* + * "SetStorage" - Saves the results of a scan for comparison to an active one. + */ + +void SetStorage ( void ) +{ + for ( int i = 0; i < DISPLAY_POINTS; i++ ) + myStorage[i] = myData[i]; + + setting.ShowStorage = true; +} + + +/* + * "SetClearStorage" - Logically clears the saved scan data + */ + +void SetClearStorage ( void ) +{ + setting.ShowStorage = false; + setting.SubtractStorage = false; +} + + +/* + * "SetSubtractStorage" + */ + +void SetSubtractStorage ( void ) +{ + if ( !setting.ShowStorage ) + SetStorage (); + + setting.SubtractStorage = true; +} + + +/* + * "RequestSetPowerLevel" will be called from the menu or WiFi, possibly Serial to request + * the offset is calculated so that peak power measured in the sweep matches the input value + * the Request function sets the flag to initiate the calibration at the start of the next sweep + * using the input value. + */ + +void RequestSetPowerLevel ( float o ) +{ + actualPower = o; + setActualPowerRequested = true; +// Serial.printf ( "Request set Power Level %f \n", o ); +} + + +/* + * "SetPowerLevel" + */ + +void SetPowerLevel ( int o ) +{ +int oldOffset = setting.LevelOffset; + + if ( o != 100 ) + setting.LevelOffset = (int) ( o - (( oldPeakLevel / 2.0 + setting.Attenuate ) - 120.0 )); // WA2FZW + else + setting.LevelOffset = 0; + +// Serial.printf ( "Peak level: %i, Actual: %i, Level offset: %i \n", +// oldPeakLevel, o, setting.LevelOffset ); + + if ( oldOffset != setting.LevelOffset ) + { + changedSetting = true; + RedrawHisto (); // Redraw labels and restart sweep with new settings + WriteSettings (); + } +} + + +/* + * "SetRBW" - Sets the resolution bandwidth in the "setting" structure. We don't + * bother to actually set it in the receiver Si4432 as that is done at the start + * of each scan cycle. + */ + +void SetRBW ( int bw ) +{ +int16_t oldRBW = setting.Bandwidth10; +int16_t tempBw; + + if ( bw == 0 ) // "AUTO" mode requested + tempBw = ( setting.ScanStop - setting.ScanStart ) / 30000; + + else + tempBw = bw; + + setting.Bandwidth10 = bw; + + bandwidth = rcvr.SetRBW ( tempBw, &delaytime ); + RedrawHisto (); // Redraw labels and restart sweep with new settings + + if ( setting.Bandwidth10 != oldRBW ) + { + changedSetting = true; + WriteSettings (); + } +} + + +/* + * "SetSpur" - Turns spurious signal supression on or off + * Also turns on the MIN trace which is needed for the spur reduction to be seen. + * Only turns the average trace off of it is still set to min + * Also turns off/on the dB trace + */ + +void SetSpur ( int v ) +{ +int oldSpur = setting.Spur; + + setting.Spur = v; + + if (setting.Spur) { + setting.Average = AV_MIN; + setting.ShowSweep = false; + } else { + if (setting.Average == AV_MIN) + setting.Average = AV_OFF; + setting.ShowSweep = true; + } + if ( oldSpur != setting.Spur ) + { + changedSetting = true; + WriteSettings (); + pushSettings (); + } +} + + +/* + * "SetAverage" - Sets the number of readings to average for each display point + */ + +void SetAverage ( int v ) +{ +int oldAvg = setting.Average; + + setting.Average = v; + + if ( oldAvg != setting.Average ) + { + changedSetting = true; + WriteSettings (); + } +} + + +/* + * "SetLoDrive" sets the transmitter 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 SetLoDrive ( uint8_t level ) +{ +int oldLevel = setting.Drive; + + if (( level < 0 ) || ( level > 7 )) + return; + + else + { + setting.Drive = level; + xmit.SetDrive ( level ); // Set transmitter power level + + if ( oldLevel != setting.Drive ) + { + changedSetting = true; + WriteSettings (); + } + } +} + + +/* + * "SetSGLoDrive" sets the transmitter max output level in signal generator mode. + * + * 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 SetSGLoDrive ( uint8_t level ) +{ +int oldLevel = sigGenSetting.LO_Drive; + + if (( level < 0 ) || ( level > 7 )) + return; + + else + { + sigGenSetting.LO_Drive = level; + + if (setting.Mode == SIG_GEN_LOW_RANGE) + xmit.SetDrive ( level ); // Set transmitter power level + + if ( oldLevel != sigGenSetting.LO_Drive ) + { + changedSetting = true; + WriteSigGenSettings (); + } + } +} + + + +/* + * "SetSGRxDrive" sets the receiver output level in signal generator mode + * + * 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 SetSGRxDrive ( uint8_t level ) +{ +int oldLevel = sigGenSetting.RX_Drive; + + if (( level < 0 ) || ( level > 7 )) + return; + + else + { + sigGenSetting.RX_Drive = level; + if (setting.Mode == SIG_GEN_LOW_RANGE) + rcvr.SetDrive ( level ); // Set transmitter power level + + if ( oldLevel != sigGenSetting.RX_Drive ) + { + changedSetting = true; + WriteSigGenSettings (); + } + } +} + + +#ifdef SI_TG_LO_CS +/* + * "SetTGLoDrive" sets the transmitter max output level for the tracking generator. + * + * 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 SetTGLoDrive ( uint8_t level ) +{ +int oldLevel = trackGenSetting.LO_Drive; + + if (( level < 0 ) || ( level > 7 )) + return; + + else + { + trackGenSetting.LO_Drive = level; + + tg_lo.SetDrive ( level ); // Set transmitter power level + + if ( oldLevel != trackGenSetting.LO_Drive ) + { + changedSetting = true; + WriteTrackGenSettings (); + } + } +} +#endif + + +#ifdef SI_TG_IF_CS +/* + * "SetTGIfDrive" sets the transmitter output level for the tracking generator IF. + * + * 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 SetTGIfDrive ( uint8_t level ) +{ +int oldLevel = trackGenSetting.IF_Drive; + + if (( level < 0 ) || ( level > 7 )) + return; + + else + { + trackGenSetting.IF_Drive = level; + tg_if.SetDrive ( level ); // Set transmitter power level + + if ( oldLevel != trackGenSetting.IF_Drive ) + { + changedSetting = true; + WriteTrackGenSettings (); + } + } +} +#endif + +/* + * Set Tracking generator IF offset from main SA IF + * Ideally TG IF should be outside bandwidth of main RX + */ +bool SetTGOffset ( int32_t offset) +{ + // check valid + if ( (offset > -1000000 ) && (offset < 1000000 ) ) + trackGenSetting.Offset = offset; + changedSetting = true; + WriteTrackGenSettings (); +} + + +/* + * Get Tracking generator IF offset from main SA IF + */ +int32_t GetTGOffset ( ) +{ + return trackGenSetting.Offset; +} + + +/* + * "SetSweepStart" and "GetSweepStart" were added in Version 2.3. They used to be in + * "switch" statements in "SetSweepFrequency" and "GetSweepFrequency". By making them + * separate function, they can be used by the "SetSweepCenter", "SetSweepSpan", etc. + * functions, thus eliminating a bunch of redundent code. + */ + +void SetSweepStart ( uint32_t startFreq ) +{ +uint32_t lastStart; // Old ssweep start frequency +uint32_t lastStop; // and old stop frequency + +// Serial.print ( "Requested start = " ); Serial.println ( FormatFrequency ( startFreq )); + + lastStart = setting.ScanStart; // Save the current start frequency + lastStop = setting.ScanStop; // and the current stop frequency + + if ( startFreq < START_MIN ) // Range check + startFreq = START_MIN; + + if ( startFreq > STOP_MAX ) // Range check + startFreq = STOP_MAX; + + if ( startFreq > setting.ScanStop ) // Start can't be more than current stop + setting.ScanStop = STOP_MAX; // So set stop at maximum + + setting.ScanStart = startFreq; // Set new start frequency + + if (( setting.ScanStart != lastStart ) + || ( setting.ScanStop != lastStop )) // Did it change? + { + changedSetting = true; // Yes it did + RedrawHisto (); // Redraw labels and restart sweep with new settings + WriteSettings (); // and save the setting structure + } +} + + +uint32_t GetSweepStart ( void ) +{ + return setting.ScanStart; +} + + +/* + * "SetSweepStop" and "GetSweepStop" were also added in Version 2.3. + */ + +void SetSweepStop ( uint32_t stopFreq ) +{ +uint32_t lastStart; // Old ssweep start frequency +uint32_t lastStop; // and old stop frequency + +// Serial.print ( "Requested stop = " ); Serial.println ( FormatFrequency ( stopFreq )); + + lastStart = setting.ScanStart; // Save the current start frequency + lastStop = setting.ScanStop; // and the current stop frequency + + if ( stopFreq < START_MIN ) // Range check + stopFreq = START_MIN; + + if ( stopFreq > STOP_MAX ) // Range check + stopFreq = STOP_MAX; + + if ( stopFreq < setting.ScanStart ) // Stop can't be less than current start + setting.ScanStart = START_MIN; // so set start to minimum frequency + + setting.ScanStop = stopFreq; // Set new stop frequency + + if (( setting.ScanStart != lastStart ) + || ( setting.ScanStop != lastStop )) // Did it change + { + changedSetting = true; // Yes it did + RedrawHisto (); // Redraw labels and restart sweep with new settings + WriteSettings (); // and save the setting structure + } +} + + +uint32_t GetSweepStop ( void ) +{ + return setting.ScanStop; +} + + +/* + * Specific start/stop frequency setting for IF Sweep mode + */ + +void SetIFsweepStart ( uint32_t startFreq ) +{ +uint32_t lastStart; // Old sweep start frequency +uint32_t lastStop; // and old stop frequency + +// Serial.print ( "Requested start = " ); Serial.println ( FormatFrequency ( startFreq )); + + lastStart = startFreq_IF; // Save the current start frequency + lastStop = stopFreq_IF; // and the current stop frequency + + if ( startFreq < IF_START_MIN ) // Range check + startFreq = IF_START_MIN; + + if ( startFreq > IF_STOP_MAX ) // Range check + startFreq = IF_STOP_MAX; + + if ( startFreq > stopFreq_IF ) // Start can't be more than current stop + stopFreq_IF = IF_STOP_MAX; // So set stop at maximum + + startFreq_IF = startFreq; // Set new start frequency + + if (( startFreq_IF != lastStart ) + || ( stopFreq_IF != lastStop )) // Did it change? + { + changedSetting = true; // Yes it did + RedrawHisto (); // Redraw labels and restart sweep with new settings +// WriteSettings (); // and save the setting structure + } +} + + +uint32_t GetIFsweepStart ( void ) +{ + return startFreq_IF; +} + + + + +void SetIFsweepStop ( uint32_t stopFreq ) +{ +uint32_t lastStart; // Old sweep start frequency +uint32_t lastStop; // and old stop frequency + +// Serial.print ( "Requested stop = " ); Serial.println ( FormatFrequency ( stopFreq )); + + lastStart = startFreq_IF; // Save the current start frequency + lastStop = stopFreq_IF; // and the current stop frequency + + if ( stopFreq < IF_START_MIN ) // Range check + stopFreq = IF_START_MIN; + + if ( stopFreq > IF_STOP_MAX ) // Range check + stopFreq = IF_STOP_MAX; + + if ( stopFreq < startFreq_IF ) // Stop can't be less than current start + startFreq_IF = IF_START_MIN; // so set start to minimum frequency + + stopFreq_IF = stopFreq; // Set new stop frequency + + if (( startFreq_IF != lastStart ) + || ( stopFreq_IF != lastStop )) // Did it change? + { + changedSetting = true; // Yes it did + RedrawHisto (); // Redraw labels and restart sweep with new settings +// WriteSettings (); // and save the setting structure + } +} + + +uint32_t GetIFsweepStop ( void ) +{ + return stopFreq_IF; +} + + +void SetIFsweepSigFreq ( uint32_t sigFreq ) +{ +uint32_t lastSigFreq; + +// Serial.print ( "Requested if sweep signal = " ); Serial.println ( FormatFrequency ( sigFreq_IF )); + + lastSigFreq = sigFreq_IF; + +// check in a sensible range + if ( ( sigFreq < 1000000 ) || (sigFreq > 100000000 ) ) + Serial.println(" Out of range - must be between 1,000,000 and 100,000,000Hz"); + else + { + sigFreq_IF = sigFreq; + if ( sigFreq_IF != lastSigFreq ) + changedSetting = true; + } + + RedrawHisto (); // Redraw labels and restart sweep with new settings + +} + + +uint32_t GetIFsweepSigFreq ( void ) +{ + return sigFreq_IF; +} + + +/* + * Specific start/stop frequency setting for Bandscope mode + */ + +void SetBandscopeStart ( uint32_t startFreq ) +{ +uint32_t lastStart; // Old sweep start frequency + +// Serial.print ( "Requested start = " ); Serial.println ( FormatFrequency ( startFreq )); + + lastStart = setting.BandscopeStart; // Save the current start frequency + + if ( startFreq < START_MIN ) // Range check + startFreq = START_MIN; + + if ( startFreq > STOP_MAX ) // Range check + startFreq = STOP_MAX; + + setting.BandscopeStart = startFreq; // Set new start frequency + + if ( setting.BandscopeStart != lastStart ) + { + changedSetting = true; // Yes it did + initSweep = true; +// RedrawHisto (); // Redraw labels and restart sweep with new settings +// WriteSettings (); // and save the setting structure + } +} + + +uint32_t GetBandscopeStart ( void ) +{ + return setting.BandscopeStart; +} + + + +/* + * Specific start/stop frequency setting for Bandscope mode + */ + +void SetBandscopeSpan ( uint32_t spanFreq ) +{ +uint32_t lastSpan; // Old sweep span + +// Serial.print ( "Requested bandscope span = " ); Serial.println ( FormatFrequency ( spanFreq )); + + lastSpan = setting.BandscopeSpan; // Save the current start frequency + + if ( spanFreq < 100000 ) // Range check + spanFreq = 100000; + + if ( spanFreq > 350000 ) // Range check + spanFreq = 350000; + + setting.BandscopeSpan = spanFreq; // Set new start frequency + + if ( setting.BandscopeSpan != lastSpan ) + { + changedSetting = true; // Yes it did + initSweep = true; +// RedrawHisto (); // Redraw labels and restart sweep with new settings +// WriteSettings (); // and save the setting structure + } +} + + +uint32_t GetBandscopeSpan ( void ) +{ + return setting.BandscopeSpan; +} + + + + +/* + * When the user sets the "CENTER" frequency, we're also going to set the "SPAN". But, + * it's a bit tricky! + * + * The basic assumption is that we will set the "SPAN" equal to the selected "CENTER" + * frequency. But if the "SPAN" limits would cause the "START" frequency to go negative + * or cause the "STOP" frequency to exceed "STOP_MAX", the difference between the + * "CENTER" frequency and whichever limit is exceeded will become 1/2 of the "SPAN". + * + * Of course, setting the "CENTER" frequency at either one of the limits is going to + * result in a zero span. + */ + +void SetSweepCenter ( uint32_t freq, uint8_t span ) +{ +uint32_t startFreq; // These are used in computing the +uint32_t stopFreq; // Scan limits +uint32_t halfSpan; // Half of the frequency span + +uint32_t lastStart; // Old ssweep start frequency +uint32_t lastStop; // and old stop frequency + +// Serial.print ( "Requested center = " ); Serial.println ( FormatFrequency ( freq )); + + lastStart = setting.ScanStart; // Save the current start frequency + lastStop = setting.ScanStop; // and the current stop frequency + + +/* + * Range check the requested frequency against the sweep limits and adjust accordingly: + */ + + if ( freq > STOP_MAX ) // Range check requested frequency + freq = STOP_MAX; + + if ( freq < START_MIN ) // Range check + freq = START_MIN; + + +/* + * Compute new start and stop frequencies based on the requested frequenct and whether + * a "WIDE" span or a "NARROW" span was requested. + */ + + if ( span == WIDE ) // Using normal span? + halfSpan = freq / 2; // Then halfSpan is half the frequency + + else // Must be a "NARROW" span + halfSpan = freq / FOCUS_FACTOR / 2; // Span is banse on the "FOCUS_FACTOR" + + startFreq = freq - halfSpan; // New computed start frequency + stopFreq = freq + halfSpan; // New computed stop frequency + + +/* + * From the Department of Redundency Department: Now we have to range check the computed + * start and stop frequencies and if either of those is out of bounds, we need to adjust the + * computed span accordingly. We'll maintain the requested center frequency though. + */ + + if ( stopFreq > STOP_MAX ) // Range check the stop frequency + { + stopFreq = STOP_MAX; // Set the stop frequency at the upper limit + halfSpan = stopFreq - freq; // Re-compute half span + startFreq = freq - halfSpan; // and recompute start frequency + } + + if ( startFreq < START_MIN ) // Range check the start frequency + { + startFreq = START_MIN; // Set start at the limit + halfSpan = freq - startFreq; // Re-compute half span + stopFreq = freq + halfSpan; // and re-compute stop frequency + } + + startFreq = freq - halfSpan; // Re-compute start and + stopFreq = freq + halfSpan; // stop frequencies + +// Serial.print ( "Computed start = " ); Serial.println ( FormatFrequency ( startFreq )); +// Serial.print ( "Computed stop = " ); Serial.println ( FormatFrequency ( stopFreq )); +// Serial.print ( "Half span = " ); Serial.println ( FormatFrequency ( halfSpan )); + + setting.ScanStart = startFreq; // Set new start frequency + setting.ScanStop = stopFreq; // Set new stop frequency + + if (( setting.ScanStart != lastStart ) + || ( setting.ScanStop != lastStop )) // Did it change + { + changedSetting = true; // Yes it did + RedrawHisto (); // Redraw labels and restart sweep with new settings + WriteSettings (); // and save the setting structure + } +} + +uint32_t GetSweepCenter () +{ + return ( setting.ScanStart + setting.ScanStop ) / 2; +} + + +/* + * "SetSweepSpan" will maintain the previous center frequency and attempt to simply + * change the range of the scan. If the new limits cause the start frequency to go + * or the stop frequency to exceed "STOP_MAX", the range will be adjusted to maintain + * an equal range on both sides of the center frequency. + */ + +void SetSweepSpan ( uint32_t spanRange ) +{ +uint32_t startFreq; // These are used in computing the +uint32_t stopFreq; // Scan limits +uint32_t centerFreq; // Current center frequency +uint32_t halfSpan; // Half of the frequency span + +uint32_t lastStart; // Old ssweep start frequency +uint32_t lastStop; // and old stop frequency + +// Serial.print ( "Requested span = " ); Serial.println ( FormatFrequency ( spanRange )); + + lastStart = setting.ScanStart; // Save the current start frequency + lastStop = setting.ScanStop; // and the current stop frequency + + halfSpan = spanRange / 2; // Half the requested span + centerFreq = GetSweepCenter (); // Current center frequency + + startFreq = spanRange - halfSpan; // New computed start frequency + stopFreq = spanRange + halfSpan; // New computed stop frequency + + +/* + * Now we have to range check the computed start and stop frequencies and if either + * of those is out of bounds, we will adjust the span accordingly. We'll maintain + * the previous center frequency though. + */ + + if ( stopFreq > STOP_MAX ) // Range check the stop frequency + { + stopFreq = STOP_MAX; // Set the stop frequency at the upper limit + halfSpan = stopFreq - centerFreq; // Re-compute half span + startFreq = centerFreq - halfSpan; // and recompute start frequency + } + + if ( startFreq < START_MIN ) // Range check the start frequency + { + startFreq = START_MIN; // Set start at the limit + halfSpan = centerFreq - startFreq; // Re-compute half span + stopFreq = centerFreq + halfSpan; // and re-compute stop frequency + } + + startFreq = centerFreq - halfSpan; // Re-compute start and + stopFreq = centerFreq + halfSpan; // stop frequencies + +// Serial.print ( "Computed start = " ); Serial.println ( FormatFrequency ( startFreq )); +// Serial.print ( "Computed stop = " ); Serial.println ( FormatFrequency ( stopFreq )); +// Serial.print ( "Half span = " ); Serial.println ( FormatFrequency ( halfSpan )); + + setting.ScanStart = startFreq; // Set new start frequency + setting.ScanStop = stopFreq; // Set new stop frequency + + if (( setting.ScanStart != lastStart ) + || ( setting.ScanStop != lastStop )) // Did it change + { + changedSetting = true; // Yes it did + RedrawHisto (); // Redraw labels and restart sweep with new settings + WriteSettings (); // and save the setting structure + } +} + + +uint32_t GetSweepSpan () +{ + return setting.ScanStop - setting.ScanStart; +} + + +/* + * "SetFreq" is used by the serial command handler to set a specific frequency + * for one of the Si4432 modules. + */ + +void SetFreq ( int vfo, uint32_t freq ) +{ + if ( vfo == RX_4432 ) // Receiver? + rcvr.SetFrequency ( freq ); + + if ( vfo == TX_4432 ) // Transmitter? + xmit.SetFrequency ( freq ); +} diff --git a/cmd.h b/cmd.h new file mode 100644 index 0000000..59c868a --- /dev/null +++ b/cmd.h @@ -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 + + +/* + * 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" diff --git a/marker.cpp b/marker.cpp new file mode 100644 index 0000000..db6430a --- /dev/null +++ b/marker.cpp @@ -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; +} diff --git a/marker.h b/marker.h new file mode 100644 index 0000000..9d9d1f4 --- /dev/null +++ b/marker.h @@ -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 diff --git a/menu.cpp b/menu.cpp new file mode 100644 index 0000000..8c8d960 --- /dev/null +++ b/menu.cpp @@ -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 + } +} diff --git a/menu.h b/menu.h new file mode 100644 index 0000000..6affa38 --- /dev/null +++ b/menu.h @@ -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 // 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 diff --git a/my_SA.h b/my_SA.h new file mode 100644 index 0000000..3b0fe44 --- /dev/null +++ b/my_SA.h @@ -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_ diff --git a/pE4302.cpp b/pE4302.cpp new file mode 100644 index 0000000..522256e --- /dev/null +++ b/pE4302.cpp @@ -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 +} diff --git a/pE4302.h b/pE4302.h new file mode 100644 index 0000000..fb649c4 --- /dev/null +++ b/pE4302.h @@ -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 // General Arduino definitions +#include // I2C library +#include // 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 diff --git a/preferences.cpp b/preferences.cpp new file mode 100644 index 0000000..0763cf9 --- /dev/null +++ b/preferences.cpp @@ -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 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" ); +} diff --git a/preferences.h b/preferences.h new file mode 100644 index 0000000..decd1fb --- /dev/null +++ b/preferences.h @@ -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 ); diff --git a/si4432.cpp b/si4432.cpp new file mode 100644 index 0000000..966adde --- /dev/null +++ b/si4432.cpp @@ -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 // Serial Peripheral Interface library +#include "Si4432.h" // Our header file +#include + +/* + * 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 )); +} diff --git a/si4432.h b/si4432.h new file mode 100644 index 0000000..ade99ba --- /dev/null +++ b/si4432.h @@ -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 // Basic Arduino definitions +#include // 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 diff --git a/simpleSA.h b/simpleSA.h new file mode 100644 index 0000000..1b382cf --- /dev/null +++ b/simpleSA.h @@ -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 // General Arduino definitions +#include + + +#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. + * + * + * + * The starting point for this version is the "tinySA_touch05" software developed + * by David (M0WID). + * That software was based on the original version for STM "Blue Pill" by Erik Kaashoek PD0EK. + * + * Modified by John Price (WA2FZW): + * + * Version 1.0: + * + * Just add comments, try to figure out how it all works and move some stuff + * around for clarity! + * + * + * Version 1.1: + * + * Added the file "Si4432.h" which contains symbols for all the Si4432 + * registers and some other things related to it's operation. + * + * Added the files "PE4302.h" and "PE4302.cpp" which provide a class/object + * implementation of the attenuator handling. One can now use either the + * serial or parallel type modules connected directly to the processor + * or the parallel module interfaced via a PCF8574 GPIO expander chip as + * Glenn (VK3PE) did on his hardware implementation. + * + * The value stored in "setting.attenuate" is now a positive number. Previously + * it was stored as a negative value and displayed as "Atten: -nn", which would + * imply a gain! The PE4302 library also range limits the attenuator setting + * to a value between 0dB and 31dB, which is the range of the PE4302. + * + * Incorporated changes from David's Version 05 release. Those changes include + * eliminating the support for the Arduino and the addition of WiFi capability. + * + * Added the file "Si4432.h" which contains symbols for all the Si4432 + * + * + * Version 1.6: + * + * Added the file "Si4432.cpp" and moved all of the functions to handle the + * interface to the hardware out of here. * + * + * Version 1.7: + * + * Moved all the global variables to the top of the file and moved the "setup" + * and "loop" functions to the top where they are customarily found. + * + * Stripped the serial interface command handler out of the "loop" function and + * created the "CheckCommand" function; made a lot of changes to how that all + * works also. + * + * + * Version 1.8: + * + * Replaced the original code to handle the Si4432 modules with a class/object + * implementation using real SPI protocol. + * + * + * Version 2.0: + * + * Overhauled the serial command handler and help menu. + * + * + * Version 2.1: + * + * A major overhaul of the entire architecture! I moved all the functions that + * handle reading and processing commands from the serial input into "Cmd.cpp". + * This is the first step into being able to use common command processing for + * the serial interface, web page and touch screen. + * + * + * Version 2.7: + * + * More restructuring. Added markers . Added more + * commands to the serial command handler. Re-organized the touch screen menus + * in a more logical hierarchy and added some new capabilities there. + * + * + * Version 2.8 Changes by M0WID: + * + * Data now pushed to the web clients + * Grid y changed to have 10 divisions + * Various bug fixes + * Layout of display changed + * New Signal Generator mode + * Preparation for other modes eg High Frequency Range + * Scale for preamp gain trace added + * Spur Reduction now does something! + * + * Version 3.0a Changes by M0WID + * Variable no of points to push to WiFi clients to give more frequent updates at narrow RBW + * and fewer chart updates at wide RBW to reduce load on slow tablet or smartphone clients + * Only check for websockets at intervals or if client connected as websockets impose a significant time penalty + * Signal Generator menu and keypad frequency entry + * Calibrate of signal generator output added to menu + * Hot spots on touch to get to specific parts of the menu faster + * + * Version 3.0e Changes by M0WID + * IF Sweep implemented to enable characterisation of the SAW filters, with its own menu + * LCD display no longer uses pin2 for reset - it is wired to the ESP32 reset instead, freeing up pin 2 for the tracking generator + * LCD display backlight no longer uses PWM to control the brightness, freeing up Pin 25 for the tracking generator. + * You can uncomment the #define in my_SA.h if you still want the PWM backlight. + * Tracking generator options added, with either one or two additional SI4432. If one SI4432 + * then the track generator LO is tapped off the existing LO and the track gen IF runs + * at the same frequency as the receiver. This can give reduced sensitivity. + * If two SI4432 are implemented then the tracking generator IF can be offset to improve rejection + * of the tracking generator IF. + * New console commands for IF Sweep and tracking generator + * New menu for tracking generator accessed from Output Menu + * SPI now runs at 16MHz (was at 1MHz - oops!) + * Additional functions in ui.cmd to allow signed frequency entry. + */ + + +#include "tinySA.h" // Definitions needed by the whole program +#include // Basic Arduino definitions +#include // Serial Peripheral Interface library +#include // I2C library +#include "Si4432.h" // Si4432 tranceiver class definitions and prototypes +#include "PE4302.h" // PE4302 attenuator class +#include "Cmd.h" // Command processing functions +#include "Marker.h" // Marker class + + +#if USE_WIFI // M0WID - We need the following if using WiFi + + #include "TinySA_wifi.h" // Our WiFI definitions + #include // ESP32 WiFi support library + #include // Asynchronous TCP library for Espressif MCUs + #include "SPIFFS.h" // ESP32 built-in "file system" + +#endif + +#include // TFT_eSPI library +#include // Library to save and restore configuration information +#include "preferences.h" // Functions to write/read settings and config + +#include "ui.h" // Touch screen interface definitions + + +/* + * Note the actual definitions for display and touch screen pins used are defined + * in the file "M0WID_Setup_ILI9341_TinySA.h" in the "User_Setups" directory of + * the "TFT_eSPI" library. + * + * These are based on using HSPI (spi2) for display and VSPI (spi3) for the SI4432s + * and the attenuator module. + * + * They are listed here just for reference; those not defined in the "TFT_eSPI" + * library are defined in the various other files comprising the program: + * + * GPIO-0 SD_CS Reserved for SD card chip select + * GPIO-1 RX0 Used by USB + * GPIO-2 TFT_RST TFT Reset; Could be set to -1 if using processor reset + * GPIO-2 tinySA_led The on-board LED flashes on data transfer + * GPIO-3 TX0 Used by USB + * GPIO-4 SI_RX_CS RX Si4432 Transceiver chip select + * GPIO-5 SI_TX_CS TX Si4432 Transceiver chip select + * GPIO-6 to 11 Reserved for the Flash memory + * GPIO-12/MISO2 TFT_MISO Display data input (to processor) + * GPIO-13/MOSI2 TFT_MOSI Display data output (from processor) + * GPIO-14/CLK2 TFT_SCLK Display SPI clock + * GPIO-15 TFT_CS Display chip select + * GPIO-16 + * GPIO-17 + * GPIO-18/CLK VSPI_SCLK Transceiver & Attenuator SPI clock + * GPIO-19/MISO VSPI_SDO Transceiver & Attenuator SPI data in (to processor) + * GPIO-21/SDA SDA PCF8575 Data line (VK3PE Implementation only) + * GPIO-21/PE4302_LE PE4302 attenuator Latch Enable (serial attenuator option) + * GPIO-22/SCL SCL PCF8575 Clock line (VK3PE Implementation only) + * GPIO-23/MOSI VSPI_SDI Transceiver & Attenuator SPI data out (from processor) + * GPIO-25 TFT_LED Display backlight intensity (PWM) + * GPIO-26 TOUCH_CS Touch screen chip select (in TFT_eSPI library setup) + * GPIO-27 TFT_DC Display data/command select line + * GPIO-32 ENC_PB Reserved for encoder pushbutton switch + * GPIO-33 TS_INT Reserved for touch screen interrupt request + * GPIO-33 ENC_BB or encoder backup button + * GPIO-34 ENC_B Reserved for encoder pin "B" (input only pin) + * GPIO-35 ENC_A Reserved for encoder pin "A" (input only pin) + * GPIO-36 Sensor_VP (input only pin) + * GPIO-39 Sensor_VN (input only pin) + * RX0 USB Receive + * TX0 USB Transmit + */ + + +/* + * The "sprites" are a method supported by the "TFT_eSPI" library that allow faster + * updates of the display and reduces flicker. We create three; one for the scan image + * proper, one for the data at the top of the screen and a separate one for the data + * at the top of the screen. We don't use one for the touch screen menus as there is + * no flicker problem there. + */ + + TFT_eSPI tft = TFT_eSPI(); // The TFT display object proper + TFT_eSprite img = TFT_eSprite ( &tft ); // Sprite for the chart drawing + TFT_eSprite tSprite = TFT_eSprite ( &tft ); // Sprite for the top of chart display + TFT_eSprite sSprite = TFT_eSprite ( &tft ); // Sprite for the side of chart display + TFT_eSprite gainScaleSprite = TFT_eSprite ( &tft ); // Sprite for the gain scale + + TFT_eSPI_Button key[SIG_KEY_COUNT]; // Used for signal generator modes for simplicity + + + +/* + + * Definition for signal generator keys + */ + + sig_key_t sig_keys[SIG_KEY_COUNT] = { + // x, y, width, height, normal colour, active colour, text + // x and y are mid points + { 18, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 0 100MHz+ + { 18+ NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 1 10MHz+ + { 18+ 2*NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 2 1MHz+ + {130, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 3 100kHz+ + {130+ NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 4 10kHz+ + {130+ 2*NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 5 1kHz+ + {237, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 6 100Hz+ +// {237+ NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 10Hz+ Pointless - SI4432 has insufficient resolution and huge drift! +// {237+ 2*NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 1Hz+ + + { 18, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 7 100MHz+ + { 18+ NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 8 10MHz+ + { 18+ 2*NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 9 1MHz+ + {130, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 10 100kHz+ + {130+ NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 11 10kHz+ + {130+ 2*NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 12 1kHz+ + {237 , 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 13 100Hz+ + // {237+ NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 10Hz+ Pointless - SI4432 has insufficient resolution and huge drift! + // {237+ 2*NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 1Hz+ + + { 45, 25, KEY_W, KEY_H, TFT_WHITE, TFT_CYAN, "SALo", "SALo"}, // 14 SALo + // { 45, 165, KEY_W, KEY_H, TFT_WHITE, TFT_GREEN, "FM", "FM on"}, // xx FM (frequency Modulation) on/off + // {105, 165, KEY_W, KEY_H, TFT_WHITE, TFT_GREEN, "AM", "AM on"}, // xx AM (amplitude modulation) on/off + {230, 175, KEY_W, KEY_H, TFT_WHITE, TFT_GREEN, "ON", "OFF"}, // 15 ON/OFF (Sig gen output on/off) + {285, 25, KEY_W, KEY_H, TFT_WHITE, TFT_CYAN, "Menu", "Menu"}, // 16 When pressed launch menu + {230, 25, KEY_W, KEY_H, TFT_WHITE, TFT_CYAN, "123", "123"} // 17 When pressed keypad to enter frequency + }; + +/* + * We create four sprites for the markers and an array of their addresses. The addresses + * will be passed to the marker objects when we initialize them in the "setup" function. + * + * It would make more sense to actually create these in the marker itself, but any attempts + * to do that caused newer versions of the "TFT_eSPI" library to crash or do goofy things. + */ + + TFT_eSprite mkr1 = TFT_eSprite ( &tft ); // Sprites for the markers + TFT_eSprite mkr2 = TFT_eSprite ( &tft ); + TFT_eSprite mkr3 = TFT_eSprite ( &tft ); + TFT_eSprite mkr4 = TFT_eSprite ( &tft ); + + TFT_eSprite* mSprites[MARKER_COUNT] = { &mkr1, &mkr2, &mkr3, &mkr4 }; + + +/* + * Create an array of un-initialized markers. We'll initialize them in the "setup" + * function. + */ + + Marker marker[MARKER_COUNT]; // Array of marker objects + + +/* + * These are used for positioning the markers: + */ + + peak_t peaks[MARKER_COUNT]; // Peaks in the current scan + peak_t oldPeaks[MARKER_COUNT]; // Peaks in the previous scan + + +/* + * The "vspi" object handles communications between the processor and the serial + * version of the PE4302 attenuator and the Si4432 modules. If we ever implement + * use of the SD card memory on the display, it will also use the VSPI bus. + * + * It's created as a pointer so it can be passed to the objects that use it. + */ + + SPIClass* vspi = new SPIClass ( VSPI ); // Create VSPI object and a pointer to it + + +/* + * Constructors for PE4302 object - which one is used depends on the type set in My_SA.h + */ + +#if ( PE4302_TYPE == PE4302_PCF) // Create the PCF8574 attenuator object + PE4302 att ( PCF8574_ADDRESS ); +#endif + +#if ( PE4302_TYPE == PE4302_GPIO ) // The parallel mode one + PE4302 att ( DATA_16, DATA_8, DATA_4, DATA_2, DATA_1, DATA_0 ); +#endif + +#if ( PE4302_TYPE == PE4302_SERIAL ) // The serial mode one + PE4302 att (vspi, PE4302_LE ); +#endif + + +/* + * Create the transceiver objects: + */ + + Si4432 rcvr ( vspi, SI_RX_CS, RX_4432 ); // Create object for the receiver + Si4432 xmit ( vspi, SI_TX_CS, TX_4432 ); // And the transmitter (local oscillator) + +#ifdef SI_TG_IF_CS + Si4432 tg_if ( vspi, SI_TG_IF_CS, TGIF_4432 ); // Create object for the tracking generator IF SI4432 +#endif + +#ifdef SI_TG_LO_CS + Si4432 tg_lo ( vspi, SI_TG_LO_CS, TGLO_4432 ); // Create object for the tracking generator LO SI4432 +#endif + + +/* + * Global definitions: + */ + +bool tgIF_OK; // true if the tracking generator IF SI4432 is pesent and initialised OK +bool tgLO_OK; // true if the tracking generator LO SI4432 is pesent and initialised OK + +uint8_t numberOfWebsocketClients; // How many connections +uint16_t wiFiPoints; // Push data to the wifi clinets when this many points collected +unsigned long wiFiTargetTime = WIFI_UPDATE_TARGET_TIME; +unsigned long websocketInterval = WEBSOCKET_INTERVAL; + +#ifdef USE_WIFI +// Json document buffers +size_t capacity = JSON_ARRAY_SIZE ( MAX_WIFI_POINTS + 1 ) + + ( MAX_WIFI_POINTS + 1 ) * JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE( 5 ); + +static DynamicJsonDocument jsonDocument ( capacity ); // Buffer for json data to be pushed to the web clients +static JsonArray Points = jsonDocument.createNestedArray ( "Points" ); // add Points array + +#endif + + +uint16_t tinySA_mode = SA_LOW_RANGE; // Low frequency range +const char *modeText[] = { "SALo", "SAHi", "SGLo", "SGHi", "IFSw", "0SpL", "0SpH", "TrGn" }; // For mode display + +float bandwidth; // The current bandwidth (not * 10) +unsigned long delaytime = 2000; // M0WID - changed to microseconds and moved here + +uint32_t steps = DISPLAY_POINTS; // Number of frequency steps in the sweep +uint32_t sweepPoints; // Number of points in the sweep. Can be more than DISPLAY_POINTS if RBW is set less than video resolution +uint32_t startFreq = 0; // Default start frequency is 0MHz +uint32_t stopFreq = 100000000; // Default stop frequency is 100MHz +uint32_t tempIF; // IF used for this sweep. Changes if Spur reduction is on + +int VFO = RX_4432; // Set current VFO for command parser to the receiver Si4432 + +int gainReading; // Current preamp gain (will vary during a scan if AGC enabled) +bool AGC_On; // Flag indicates if Preamp AGC is enabled +uint8_t AGC_Reg; // Fixed value for preampGain if not auto + +uint8_t showRSSI = 0; // When zero don't show it (serial output only) + +int changedSetting = true; // Something in the "setting" structure changed +int updateSidebar = false; // Flag to indicate update of sidebar is needed + +// UI functions +extern uint8_t ui_mode; // What mode we are in (in "ui.cpp") +extern void StartSigGenMenu ( void ); // Function to launch sig gen menu +extern void StartSigGenFreq ( void ); // Function to set frequency +extern void ResetSAMenuStack ( void ); // Reinitialise stack for SA mode +extern void ResetIFsweepMenuStack ( void ); // Reinitialise stack for IF Sweep mode +extern void ResetBandscopeMenuStack ( void ); // Reinitialise stack for Bandscope mode + +extern void ShowSplash ( void ); // Displays the splash screen + +extern void pushSettings (); +extern void pushIFSweepSettings (); +extern void pushBandscopeSettings (); + + +/* + * Variables for IFSweep Mode + */ +uint32_t startFreq_IF = IF_SWEEP_START; +uint32_t stopFreq_IF = IF_SWEEP_STOP; +uint32_t sigFreq_IF = 20000000; // 30 Mhz reference + + +/* + * Variables for timing websocket event checks: + */ + +unsigned long lastWebsocketMicros; // For timing between websocket events +unsigned long loopStartMicros; // For timing the scan +//unsigned long loopMicros; // To report the scan time + + +/* + * Variables for measuring sweep time: + */ + +unsigned long sweepStartMicros; // For timing the scan +unsigned long lastSweepStartMicros; // For timing the scan +unsigned long sweepMicros; // To report the scan time + +uint32_t sweepCount; // Used to inhibit handling Wifi until + // two sweeps have been done and WiFi + // has stabilized + + +/* + * These arrays contain the data for the various scan points; they are used in + * here and in the "TinySA_wifi" modules: + */ + +uint8_t myData[DISPLAY_POINTS+1]; +uint8_t myStorage[DISPLAY_POINTS+1]; +uint8_t myActual[DISPLAY_POINTS+1]; +uint8_t myGain[DISPLAY_POINTS+1]; // Preamp gain +uint32_t myFreq[DISPLAY_POINTS+1]; // Frequency for XML file + +uint16_t peakLevel; // Current maximum signal level +uint16_t oldPeakLevel; // Old maximum signal level +uint16_t peakIndex; +uint16_t peakGain; // Actual gain at the peak (ie minimum gain) + +static int old_settingAttenuate = -1000; +static int old_settingPowerGrid = -1000; +static int old_settingMax = -1; +static int old_settingMin = -1; +static long old_startFreq = -1; +static long old_stopFreq = -1; +static int ownrbw = 0; +static int old_ownrbw = -1; +static int vbw = 0; +static int old_vbw = -1; +static int old_settingAverage = -1; +static int old_settingSpur = -100; +static int old_bandwidth = 0; + +int16_t standalone = true; +uint16_t spacing = 10000; + +bool paused = false; + +bool sigGenOutputOn = false; +uint32_t oldFreq; // to store the current Signal Generator frequency + +/* + * "setActualPowerRequested" is set by menu or WiFi. + * When request is set the peak value during the sweep is used to calculate + * the offset needed to make the power reading match a user input level + * A calibration tool + */ + +bool setActualPowerRequested = false; +float actualPower = CAL_POWER; // Defined in "My_SA.h" + +int initSweep = true; // Flag to indicate sweep needs to restart from the beginning + // Set when sweep settings change +int16_t sweepStartDone = false; // Ensure initialize of sweep is only done once + + +/* + * All the following deal with the optional rotary encoder which could be used + * to handle the menu options. + * + * Note - None of the encoder stuff has been implemented yet, but we'll leave the + * definitions here for the time being: + * + * Symbols for button events and states (change to UC if we ever use them): + */ + +#ifdef USE_ROTARY // Not defined anywhere! + +enum buttont_event { shortClickRelease=1, longClick=2, + longClickRelease=3, shortBackClickRelease=4, + longBackClick=5, longBackClickRelease=6, + buttonRotateUp=7, buttonRotateDown=8 }; + +enum button_state { buttonUp, buttonDown, buttonLongDown }; + +int buttonState = buttonUp; // The current reading from the encoder switch +int backButtonState = buttonUp; // The current reading from the backup button +int buttonEvent = 0; // Short click release? +int lastButtonRead = HIGH; // The previous reading from the encoder switch +int lastBackButtonRead = HIGH; // The previous reading from the backup button + +uint32_t lastDebounceTime = 0; // The last time the encoder switch was toggled +uint32_t debounceDelay = 50; // The debounce time; increase if the output flickers +uint32_t longPressDelay = 350; // The long press time; increase if the output flickers + +int32_t incr; +int32_t incrBase = 1000000; +int incrBaseDigit = 7; + +#endif // "#ifdef USE_ROTARY" + +settings_t setting; // Structure to track & save settings +sigGenSettings_t sigGenSetting; // settings for signal generator mode +trackGenSettings_t trackGenSetting; // parameters for tracking gen mode + +Preferences preferences; // "preferences" is an object to enable + // saving data to Flash + + +/* + * "setup" initializes everything: + */ + +void setup () +{ +bool fsStatus = false; // True if SPIFFS loads ok + + Serial.begin ( 115200 ); // Start up the USB connection + + tft.begin (); // Start the display object + tft.setRotation ( 3 ); // If upside down, change to '1' + +/* + * Disabling the use of the PSRAM on the WROVER boards for the TFT_eSPI library + * should speed things up slightly. We do the same thing for the sprites. + */ + + tft.setAttribute ( PSRAM_ENABLE, false ); // Forces all future Sprites to be created in the on-board RAM area + + preferences.begin ( "tinySA", false ); // We retreive stuff from flash memory + ReadConfig (); // Read menu and touch settings + ReadSettings(); // Read attenuation, level adjustment etc + ReadSigGenSettings(); // Values for signal generator mode + ReadTrackGenSettings(); // Values for tracking generator mode + + setting.ShowStorage = false; // Display stored scan (on or off) + setting.SubtractStorage = false; // Subtract stored scan (on or off) + + +/* + * The touch screen needs to be calibrated. In previous versions, the instructions + * were to un-comment the call to the "TouchCalibrate" here the first time you + * ran the software and to insert the numbers it gave you into the code. + * + * This is no longer necessary as the configuration can now be done from the touch + * acreen menu system, but we leave it here as some displays are nowhere near correct + * and if they are way off, you won't be able to access the touch menus at all. + * + * The "config.touch_cal" fed into the "tft.setTouch" function is also reacalled + * from the flash memory upon startup, so one should only need to calibrate the + * touch screen one time. + */ + +// TouchCalibrate (); // Comment out after first run + + tft.setTouch ( config.touch_cal ); // Send calibration data to the display + +#ifdef BACKLIGHT_LEVEL + setUpLEDC(); // Set up the backlight control +#endif + + +/* + * Display the splash screen: + */ + + ShowSplash (); // Display the splash screen + + +/* + * Set up the pins used for the VSPI bus used by both SI4432 modules and the serial + * mode PE4302 attenuator module and initialize the bus object: + */ + + pinMode ( V_SCLK, OUTPUT ); // SPI Clock is an output + pinMode ( V_SDO, INPUT ); // SDO (MISO) is an input + pinMode ( V_SDI, OUTPUT ); // SDI (MOSI) is an output + + 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 + + // SI4432 SPI rated for 10MHz according to data sheet. Seems OK at 16MHz + vspi->setFrequency(BUS_SPEED); // run at 10MHz + + +// tft.println("VSPI started"); + + +/* + * Initialize the SI4432 for RX and LO(TX) + */ + + tft.setCursor(0,160); +// tft.println ( "Initializing TX SI4432" ); + + if ( ! xmit.Init ( config.TX_capacitance ) ) // Initialize the transmitter module + DisplayError ( ERR_FATAL, + "LO SI4432 failed", + "to initialise", NULL, NULL ); + +// tft.println( "Initializing RX SI4432" ); + + if ( ! rcvr.Init ( config.RX_capacitance ) ) // Initialize the receiver module + DisplayError ( ERR_FATAL, + "RX SI4432 failed", + "to initialise", NULL, NULL ); + + bandwidth = rcvr.SetRBW ( setting.Bandwidth10, &delaytime ); // Set initial bandwidth and delaytime + SetRX ( 0 ); // RX in receive, LO in transmit + rcvr.SetPreampGain ( setting.PreampGain ); + + +// If the tracking generator IF SI4432 is defined, then check to see if it is connected + tgIF_OK = false; +#ifdef SI_TG_IF_CS +// pinMode (SI_TG_IF_CS, INPUT); //Start off in input mode, no pullup +// delay(2); +// if (digitalRead (SI_TG_IF_CS) ) { +// // Tracking gen IF present - initialise it +// pinMode (SI_TG_IF_CS, OUTPUT); +// delay(2); +// tgIF_OK = tg_if.Init ( config.tgIF_capacitance ); + + if ( tg_if.Init ( config.tgIF_capacitance ) ) + { + tft.println ("Tracking IF initialised"); + tgIF_OK = true; + } + else + { + DisplayError ( ERR_WARN, + "Track gen IF SI4432 failed", + "to initialise", NULL, NULL ); + tgIF_OK = false; + } +// } +// else +// { +// Serial.println("Track gen IF SI4432 not detected"); +// pinMode (SI_TG_IF_CS, OUTPUT); +// digitalWrite(SI_TG_IF_CS, HIGH); // make sure it doesn't get selected +// } +#else + pinMode (2, OUTPUT); + digitalWrite(2, HIGH); // make sure it doesn't get selected +#endif + + + +// If the tracking generator LO SI4432 is defined, then check to see if it is connected + tgLO_OK = false; +#ifdef SI_TG_LO_CS +// pinMode (SI_TG_LO_CS, INPUT); //Start off in input mode, no pullup +// delay(2); +// if (digitalRead (SI_TG_LO_CS) ) { +// // Tracking gen LO present - initialise it, but not in TX mode yet +// pinMode (SI_TG_LO_CS, OUTPUT); +// delay(2); + tgLO_OK = tg_lo.Init ( config.tgLO_capacitance ); + if ( tgLO_OK ) + tft.println ("Tracking LO initialised"); + else + DisplayError ( ERR_WARN, + "Track gen LO SI4432 failed", + "to initialise", NULL, NULL ); +// } +// else +// { +// Serial.println("Track gen LO SI4432 not detected"); +// pinMode (SI_TG_LO_CS, OUTPUT); +// digitalWrite(SI_TG_LO_CS, HIGH); // make sure it doesn't get selected +// } + +#endif + + +/* + * Initialize the SPIFFS system + */ + + fsStatus = SPIFFS.begin (); // Mount the file system + + if ( !fsStatus ) // If we can't access the file system + { + Serial.println ( "SPIFFS Mount Failed" ); // Error message to serial output + + DisplayError ( ERR_WARN, + "Failed to mount SPIFFS", + "Use ESP sketch Data Upload", + "In the Arduino IDE", + "WiFi Disabled" ); + } + +// printSPIFFS (); // Print out the contents of SPIFFS to the serial port + +#if ( USE_WIFI ) // see My_SA.h + + if ( fsStatus ) // If we can access the file system + { + #ifdef USE_ACCESS_POINT // If using a WiFi access point + + startAP (); // Start it up + + #else // Connect to the router using SSID + // and password defined in TinySA.h + + Serial.println ( "Connecting..." ); // Indicate trying to connect + + if ( connectWiFi () ) // Connection established? + Serial.println( "Connected" ); // We are connected! + else + Serial.println( "Connection Failed!" ); + + #endif // "#ifdef USE_ACCESS_POINT" + + +/* + * Start the websockets server and event handler + */ + +// tft.println("Starting Websockets"); + + webSocket.begin (); + webSocket.onEvent ( webSocketEvent ); + Serial.println ( "WebSockets started" ); + websocketInterval = WEBSOCKET_INTERVAL; + +// tft.println("Building Server"); + + buildServer (); + + IPAddress ipAddress = WiFi.localIP (); // Get our IP address + + Serial.print ( "Setup - WiFi access point started - browse to http://" ); + Serial.println ( ipAddress.toString().c_str() ); + } + +#endif // "#if ( USE_WIFI )" + + if ( !USE_WIFI || !fsStatus ) // If the WiFI option is not enabled + Serial.println ( "\nWiFi not enabled!\n" ); + + +// Serial.println ( "Initializing PE4302" ); + +// tft.println("Initialising PE4302"); + + att.Init (); // Initialize the PE4302 attenuator + att.SetAtten ( 0 ); // Set the attenuation to zero + +// tft.println("Setup Complete"); + + Serial.printf ( "\nTinySA Version %s Initialization Complete\n", PROGRAM_VERSION ); + + ShowMenu (); // Display the menu of commands on serial monitor + + +/* + * Initialize the markers. The objects need the address of the display object and + * the individual marker objects previously created. + */ + + for ( int i = 0; i < MARKER_COUNT; i++ ) + { + marker[i].Init ( mSprites[i], i + 1 ); // Pass in the sprite object + marker[i].Status ( setting.MkrStatus[i] ); // Set color and enabled status + } + + + delay ( 3000 ); // Time to read splash and initialize messages - Read fast! + ClearDisplay (); + + setMode ( setting.Mode ); // setMode initializes stuff for the selected mode + + lastWebsocketMicros = micros(); + +} // End of "setup" + + +/* + * ######################################################################################## + * + * "loop" runs forever and handles all the things needed to make it work. + * + * ####################################################################################### + */ + +void loop () +{ + +loopStartMicros = micros(); + +#if USE_WIFI +//Serial.println("l"); + if ( ( numberOfWebsocketClients > 0) || (loopStartMicros - lastWebsocketMicros > websocketInterval) + || (loopStartMicros < lastWebsocketMicros) ) // handles rollover of micros() + { +// Serial.print("L"); + webSocket.loop (); // Check websockets for events, but not at first +// Serial.println("l"); + lastWebsocketMicros = loopStartMicros; + } + +#endif + + CheckCommand (); // Anything from the serial input? + + if ( ( setting.Mode != SIG_GEN_LOW_RANGE ) || (ui_mode != UI_NORMAL ) ) + { + UiProcessTouch (); // Handle the touch screen + if ( ui_mode != UI_NORMAL ) + { + initSweep = true; + return; // If in menu don't do anything else + } + } + +/* + * Not fully implemented yet. + */ + + switch ( setting.Mode ) + { + case SA_LOW_RANGE: + doSweepLow(); // Spectrum Analyser Low Frequency range + break; + + case SA_HIGH_RANGE: + doSweepHigh(); // Spectrum Analyser High Frequency range + break; + + case SIG_GEN_LOW_RANGE: + doSigGenLow(); // Signal Generator Low Frequency range + break; + + case SIG_GEN_HIGH_RANGE: + doSigGenHigh(); // Signal Generator High Frequency range + break; + + case IF_SWEEP: + doIF_Sweep(); // IF Sweep + break; + + case BANDSCOPE: + doBandscope(); // Bandscope Sweep + break; + + default: + DisplayError ( ERR_WARN, + "Invalid Mode!", + "Mode changed to", + "Analayze Low range", NULL ); + initSweepLow (); + + } // end of switch +} // end of loop + + +/* + * SetMode initializes various things for the selected mode then changes mode. It is called + * by the web interface, command line or menu. + */ +void setMode ( uint16_t newMode ) +{ + switch ( newMode ) + { + case SA_LOW_RANGE: + initSweepLow(); + break; + +// case SA_HIGH_RANGE: +// initSweepHigh(); +// break; + + case SIG_GEN_LOW_RANGE: + initSigLow(); + break; + +// case SIG_GEN_HIGH_RANGE: +// initSigHigh(); +// break; + + case IF_SWEEP: + initIF_Sweep(); + break; + + case BANDSCOPE: + initBandscope(); + break; + + default: + DisplayError ( ERR_WARN, + "Invalid Mode!", + "Mode not changed", + NULL, NULL ); + } +} + + +/* + * "menuExit" is called when the user leaves the menu + * The required action depends on the selected mode, and whether or not the + * device is already in the selected mode + */ + +void menuExit() +{ + switch ( tinySA_mode ) + { + case SA_LOW_RANGE: + if ( setting.Mode == SA_LOW_RANGE ) + RedrawHisto(); + else + initSweepLow(); + break; + + case SA_HIGH_RANGE: + initSweepHigh(); + break; + + case SIG_GEN_LOW_RANGE: + initSigLow(); + break; + + case SIG_GEN_HIGH_RANGE: + initSigHigh(); + break; + + case IF_SWEEP: + if ( setting.Mode == IF_SWEEP ) + RedrawHisto(); + else + initIF_Sweep(); + break; + + case BANDSCOPE: + if ( setting.Mode == BANDSCOPE ) + RedrawHisto(); + else + initBandscope(); + break; + + default: + // add handler here + break; + + } // end of switch +} + + +/* + * Initialise common to low, high and IF_Sweeps + */ +void init_sweep() +{ + 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 ); + + +/* + * The "sSprite" is used for displaying the data to the side of the scan grid. + */ + + sSprite.unloadFont(); + sSprite.deleteSprite(); + sSprite.setTextSize ( 1 ); + sSprite.setColorDepth ( 16 ); + + sSprite.setAttribute ( PSRAM_ENABLE, false ); // Don't use the PSRAM on the WROVERs +/* + * 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 ); + +#ifdef SI_TG_IF_CS + if (tgIF_OK) { + tg_if.TxMode ( trackGenSetting.IF_Drive ); // turn on the IF oscillator in tracking generator + } +#endif + + +#ifdef SI_TG_LO_CS + if (tgLO_OK) { + tg_lo.TxMode ( trackGenSetting.LO_Drive ); // turn on the Local Oscillator in tracking generator + } +#endif + + sweepStartDone = false; // Make sure this initialize is only done once per sweep + initSweep = true; +} + + + +/* + * Initialise high frequency mode sweep. + */ +void initSweepHigh () +{ + // To be done! + + DisplayError ( ERR_WARN, + "Sweep not done!", + "Mode changed to", + "Analayze Low range", NULL ); + + tinySA_mode = SA_LOW_RANGE; + setting.Mode = tinySA_mode; +} + + +/* + * High freq range sweep - not yet implemented + * + * Will use signals direct into LO SI4432, which be in receive + * no mixer, attenuator or low pass filter + */ + +void doSweepHigh () +{ + DisplayError ( ERR_WARN, + "Sweep High Range", + "not implemented.", + "Low range mode", + "selected for you!" ); + + initSweepLow(); +} + + + +/* + * Initialise sig gen high frequency mode. + * May turn out to not be needed! + */ +void initSigHigh () +{ + // To be done! + + DisplayError ( ERR_WARN, + "IF Sweep not done!", + "Mode changed to", + "Analayze Low range", NULL ); + + tinySA_mode = SA_LOW_RANGE; + setting.Mode = tinySA_mode; +} + + + +/* + * High freq range signal generator - not yet implemented + * May turn out to be included in sig gen mode and auto + * transition depending on set frequency + */ + +void doSigGenHigh () +{ + DisplayError ( ERR_WARN, + "Signal Generator High Range", + "not implemented.", + "Low range SA mode", + "selected for you!" ); + + initSweepLow(); +} + + + + +/* + * "SetRX" - Mode 3 is sig gen, 0 is normal (RX as receive, LO on), 1 is both + * receiving 2 not used + */ + +void SetRX ( int p ) +{ + int saRX = p; + + if ( saRX == ( 3 )) // Both on TX - sig gen mode + { + rcvr.TxMode ( sigGenSetting.RX_Drive ); // Put receive module in TX mode + xmit.TxMode ( sigGenSetting.LO_Drive ); // Put transmit module in TX mode + } + + else + { + if ( saRX == 0 ) // Normal configuration + { + rcvr.RxMode (); // Put receive module in RX mode + xmit.TxMode ( setting.Drive ); // Put transmit (LO) module in TX mode + } + + else if ( saRX == 1 ) // Both in receive mode + { + rcvr.RxMode (); // Put receive module in RX mode + xmit.RxMode (); // Put transmit module in RX mode + } + + else if ( saRX == 2 ) // Normal configuration + { + rcvr.RxMode (); // Put receive module in RX mode + xmit.TxMode ( setting.Drive ); // Put transmit module in TX mode + } + } +} + + +/* + * "ClearDisplay" - Function to clear the display. + */ + +void ClearDisplay () +{ + tft.fillScreen ( BLACK ); // Fade to black +} + + +/* + * "textWhite" sets the text size to '1' and the text color to "WHITE". + */ + +void textWhite() +{ + tft.setTextSize ( 1 ); + tft.setTextColor ( WHITE ); // Draw white text (transparent background) +} + + +/* + * Find out if the touch was in the bounds of the slider + */ + bool sliderPressed( bool pressed, uint16_t t_x, uint16_t t_y ) + { + uint16_t minX = SLIDER_X - 5; + uint16_t maxX = SLIDER_X + SLIDER_WIDTH + 2 * SLIDER_KNOB_RADIUS + 5; + uint16_t minY = SLIDER_Y; + uint16_t maxY = SLIDER_Y + 2 * SLIDER_KNOB_RADIUS; + + return( ( (t_x >= minX) && (t_x <= maxX) && (t_y >= minY) && (t_y <= maxY) ) && pressed ); + } + +float sliderPercent( uint16_t t_x ) +{ + float p = (float)(t_x - SLIDER_X - SLIDER_KNOB_RADIUS) * 100.0 / (float)SLIDER_WIDTH; + //Serial.printf("t_x: %i p: %f SLIDER_X: %i SLIDER_KNOB_RADIUS: %i \n", t_x, p, SLIDER_X, SLIDER_KNOB_RADIUS); + if ( p > 100.0 ) + p = 100.0; + if ( p < 0.0 ) + p = 0.0; + return( p ); +} + + +/* + * Draw a slider control. Uses the sSprite. + */ + +void drawSlider (uint16_t x, uint16_t y, float pos, float value, const char *unit) { + + // range check + float p = pos; + if (p < 0) p=0.0; + if (p > 100.0) p=100.0; + + // draw a rectangle first, then bar, then position the "knob" on top + sSprite.fillSprite(SIG_BACKGROUND_COLOR); + sSprite.fillRoundRect( SLIDER_KNOB_RADIUS, ( sSprite.height() - SLIDER_BOX_HEIGHT ) / 2, + SLIDER_WIDTH, SLIDER_BOX_HEIGHT, SLIDER_BOX_HEIGHT/4, SLIDER_BOX_COLOR); + + uint16_t knob_x = pos * SLIDER_WIDTH / 100 + SLIDER_KNOB_RADIUS; + sSprite.fillRoundRect( SLIDER_KNOB_RADIUS, ( sSprite.height() - SLIDER_BOX_HEIGHT ) / 2, + knob_x - SLIDER_KNOB_RADIUS, SLIDER_BOX_HEIGHT, SLIDER_BOX_HEIGHT/4, SLIDER_FILL_COLOR); + + + sSprite.fillCircle(knob_x, sSprite.height()/2, SLIDER_KNOB_RADIUS, SLIDER_KNOB_COLOR); + + // Draw the value + sSprite.setCursor(SLIDER_WIDTH + 2 * SLIDER_KNOB_RADIUS + 1, SLIDER_KNOB_RADIUS - 3); + sSprite.printf("%3.0f %s", value, unit); + + +// Serial.println("drawSlider"); + sSprite.pushSprite(SLIDER_X, SLIDER_Y); +} + +/* + * "DisplayError" - Added by M0WID to display an error message of up to three lines + * on the display. + * + * Modified in Version 2.7 by WA2FZW: + * + * Added another line and error levels. Also use a much better looking font + * to display the error message! Instead of painting the entire screen in + * the appropriate color for the error level, we use a rounded filled rectangle + * around the message; looks really nice! + */ + +void DisplayError ( uint8_t severity, const char *line1, const char *line2, const char *line3, const char *line4 ) +{ + char line0[20]; // Line 0 of the message + const char *lines[] = { line0, line1, line2, line3, line4 }; // Pointers to the lines + + ClearDisplay (); // Fade to black + + if ( severity == ERR_INFO ) // Informational message? + { + strcpy ( line0, "Information:" ); // Set line 0 + tft.fillRoundRect ( 0, 40, 320, 150, 10, WHITE ); // White background + } + + else if ( severity == ERR_WARN ) // Warning message? + { + strcpy ( line0, "Warning:" ); // Set line 0 + tft.fillRoundRect ( 0, 40, 320, 150, 10, YELLOW ); // Yellow background + } + + else if ( severity == ERR_FATAL ) // Fatal error? + { + strcpy ( line0, "Fatal Error:" ); // Set line 0 + tft.fillRoundRect ( 0, 40, 320, 150, 10, RED ); // Red background + } + + tft.setTextColor ( BLACK ); // Black text + tft.setFreeFont ( &FreeSansBold9pt7b ); // Select nice font + tft.setCursor ( 20, 70 ); // Cursor for the first line + + for ( int i = 0; i < 5; i++ ) // Display the lines + { + if ( lines[i] != NULL ) // Ignore any NULL lines + { + tft.print ( lines[i] ); + tft.setCursor ( 20, 100 + ( i * 20 )); // Cursor on the next line + } + } + + delay ( DELAY_ERROR * 1000 ); // Allow time to read it + + if ( severity == ERR_FATAL ) // If fatal error + while ( true ) {} // Halt execution + + ClearDisplay (); + + tft.setFreeFont ( NULL ); // Set everything back to the default font + tft.setTextColor ( WHITE ); // and default text color + tft.setTextDatum ( TL_DATUM ); // and default position datum +} + + +/* + * "RedrawHisto" - Reset all old values to force redraw of labels + * Called when exiting menu and should be called when changed from web + */ + +void RedrawHisto () +{ + delayMicroseconds(200); // short delay to make sure any SI4432 frequency updates are completed + initSweep = true; + memset ( peaks, 0, sizeof ( peak_t )); // peaks are copied to oldPeaks at start of sweep + old_settingAttenuate = 1000; + old_settingPowerGrid = -1000; + old_settingMax = -1; + old_settingMin = -1; + old_startFreq = -1; + old_stopFreq = -1; + old_settingAverage = -1; + old_settingSpur = -100; + ClearDisplay(); + DisplayInfo(); + DrawFullCheckerBoard(); +} + + +/* + * "DisplayInfo" - Draws the background text around the checkerboard. Called + * when a setting is changed to set axis labels and top info bar + */ + +void DisplayInfo () +{ +const char *averageText[] = { " OFF", " MIN", " MAX", " 2", " 4", " 8" }; +const char *referenceOutText[] = { " 30", " 15", " 10", " 4", " 3", " 2", " 1" }; + +double fStart; +double fCenter; +double fStop; + +// 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 }; + + tSprite.fillSprite ( BLACK ); + tSprite.setTextColor ( WHITE ); + + +/* + * Update side bar info + */ + + if ( initSweep || changedSetting || updateSidebar ) + { +// Serial.println ( "DisplayInfo - InitSweep True" ); + + sSprite.createSprite ( X_ORIGIN, SCREEN_HEIGHT ); + + sSprite.fillSprite( BLACK ); + + sSprite.setCursor ( 0, 0 ); + + sSprite.setTextColor ( WHITE ); + sSprite.print ( setting.PowerGrid ); + sSprite.println ( "dB" ); + + sSprite.setCursor ( 0, CHAR_HEIGHT * 2 ); + sSprite.setTextColor ( DB_COLOR ); + sSprite.printf ( "%4i", setting.MaxGrid ); + + sSprite.setCursor ( 0, GRID_HEIGHT + Y_ORIGIN ); + sSprite.printf ( "%4i", setting.MinGrid ); + + sSprite.setTextColor ( WHITE ); + sSprite.setCursor ( 0, HALF_CHAR_H * 8 ); + + sSprite.setTextColor ( GREEN ); + + if ( setting.Bandwidth10 !=0 ) // 0 = auto + sSprite.setTextColor ( ORANGE ); + + sSprite.println ( "RBW" ); + + if ( bandwidth >= 100 ) + sSprite.printf ( "%4.0f\n", bandwidth ); + + else + sSprite.printf ( "%4.1f\n", bandwidth ); + + sSprite.setTextColor ( WHITE ); + sSprite.setCursor ( 0, HALF_CHAR_H * 13 ); + sSprite.printf ( "VRe\n%4i", vbw ); // Video resolution (not VBW) + + sSprite.setCursor ( 0, HALF_CHAR_H * 18 ); + + if ( setting.PreampGain == 0x60 ) // AGC on + { + sSprite.setTextColor ( GREEN ); + sSprite.println ( "Gain" ); + sSprite.print ( "auto" ); + } + + else + { + sSprite.setTextColor ( ORANGE ); // Show value in red if not auto + sSprite.println ( "Gain" ); + + int lnaGain = 5; // Assume low gain + + if ( setting.PreampGain & LNAGAIN ) // If LNA is turned on + lnaGain = 25; // High range + + int pgaGain = ( setting.PreampGain & PGAGAIN ) * 3; // 3dB per bit + sSprite.printf ( "%4i", lnaGain + pgaGain ); + } + + sSprite.setTextColor ( WHITE ); + + sSprite.setCursor ( 0, HALF_CHAR_H * 23 ); + sSprite.printf ( "Attn\n%4i", setting.Attenuate ); + + sSprite.setCursor ( 0, HALF_CHAR_H * 28 ); // Start at top-left corner + sSprite.printf ( "Ext\n%4.1f", 0.0 ); // Place holder for external gain/attenuation value + sSprite.setTextColor ( WHITE ); + + if ( setting.Average ) + sSprite.setTextColor ( AVG_COLOR ); + + sSprite.setCursor ( 0, HALF_CHAR_H * 33 ); + sSprite.println ( "Avg" ); + sSprite.print ( averageText[setting.Average] ); + sSprite.setTextColor ( WHITE ); + + sSprite.setCursor ( 0, HALF_CHAR_H * 38 ); + sSprite.println ( "Spur" ); + + if ( setting.Spur ) + sSprite.print ( " ON" ); + else + sSprite.print ( " OFF" ); + + sSprite.setCursor ( 0, HALF_CHAR_H * 43 ); + sSprite.println ( "Ref" ); + + if ( setting.ReferenceOut >= 0 && setting.ReferenceOut < 7 ) + sSprite.print ( referenceOutText[setting.ReferenceOut] ); + else + sSprite.print ( " OFF" ); + + sSprite.setCursor ( 0, HALF_CHAR_H * 48 ); + sSprite.println ( "Trk" ); + + if ( trackGenSetting.Mode == 1 ) + sSprite.print ( " ON" ); + else if (trackGenSetting.Mode == 2 ) + sSprite.print ( " SIG" ); + else + sSprite.print ( " OFF" ); + + + if ( USE_WIFI ) + { + sSprite.setCursor ( 0, HALF_CHAR_H * 53 ); // Show number of connected clients + sSprite.printf ( "Web\n%4u", numberOfWebsocketClients ); + } + + sSprite.pushSprite ( 0, 0 ); + sSprite.deleteSprite(); // Save memory + } // End of "if ( initSweep || changedSetting )" + + + + +/* + * Update frequency labels at bottom if changed + */ + + tft.setTextColor ( WHITE,BLACK ); + tft.setTextSize ( 1 ); + + if (tinySA_mode == IF_SWEEP) { + fStart = startFreq_IF/1000000.0; + fCenter = (double) ((( startFreq_IF + stopFreq_IF ) / 2.0 ) / 1000000.0 ); + fStop = stopFreq_IF/1000000.0; + } + else + { + fStart = (( (double)setting.ScanStart )/ 1000000.0); // Start freq + fCenter = (double) ((( setting.ScanStart + setting.ScanStop ) / 2.0 ) / 1000000.0 ); + fStop = (( (double)setting.ScanStop )/ 1000000.0 ); // Stop freq + + } + + if ( old_startFreq != fStart || old_stopFreq != fStop ) + { + tft.fillRect ( X_ORIGIN, SCREEN_HEIGHT - + CHAR_HEIGHT, SCREEN_WIDTH - X_ORIGIN - 1, SCREEN_HEIGHT - 1, BLACK ); + + // Show operating mode + tft.setCursor ( X_ORIGIN + 50, SCREEN_HEIGHT - CHAR_HEIGHT ); + tft.setTextColor ( DB_COLOR ); + tft.printf ( "Mode:%s", modeText[setting.Mode] ); + tft.setTextColor ( WHITE ); + + tft.setCursor ( X_ORIGIN + 2, SCREEN_HEIGHT - CHAR_HEIGHT ); + + tft.print ( fStart ); +// tft.print( "MHz" ); + + tft.setCursor ( SCREEN_WIDTH - 37, SCREEN_HEIGHT - CHAR_HEIGHT ); + tft.print ( fStop ); +// tft.print ( "MHz"); + + +/* + * Show the center frequency: + */ + tft.setCursor ( SCREEN_WIDTH / 2 - 40 + X_ORIGIN, SCREEN_HEIGHT - CHAR_HEIGHT ); + tft.print ( fCenter ); + tft.print ( "(MHz)" ); + + old_startFreq = fStart; // Save current frequency range + old_stopFreq = fStop; // For next time + } + + tft.setCursor ( 220, SCREEN_HEIGHT - CHAR_HEIGHT ); // Show sweep time + tft.printf ( "%6ums", sweepMicros / 1000 ); + +// tft.setCursor ( 100, SCREEN_HEIGHT - CHAR_HEIGHT ); // Show number of connected clients +// tft.print( numberOfWebsocketClients ); + + +/* + * We use the "tSprite" to paint the data at the top of the screen to avoid + * flicker. + */ + + tSprite.setCursor ( 0, 0 ); + tSprite.print ( "/" ); // Nasty! + + +/* + * Show marker values: + * + * The "xPos" and "yPos" arrays are the coordinates of where to place the marker data. + * + * The "posIndex" variable keeps track of the next available position for the marker + * data. If we want fixed positions for each marker, then change the "xPos" and "yPos" + * indicies to use "m". + */ + + int xPos[MARKER_COUNT] = { 20, 20, 160, 160 }; + int yPos[MARKER_COUNT] = { 0, CHAR_HEIGHT, 0, CHAR_HEIGHT }; + int posIndex = 0; + + for ( int m = 0; m < MARKER_COUNT; m++ ) + { + tSprite.setCursor ( xPos[m], yPos[m] ); + if (( marker[m].isEnabled()) && ( setting.ShowSweep || setting.Average != AV_OFF )) + { + tSprite.setTextColor ( WHITE ); + tSprite.printf ( "%u:%5.1fdBm %8.4fMHz", marker[m].Index()+1, + rssiTodBm ( oldPeaks[m].Level ), oldPeaks[m].Freq / 1000000.0 ); + } + else + { + tSprite.setTextColor ( DARKGREY ); + tSprite.printf ( "%u:", marker[m].Index()+1 ); + } + posIndex++; + } + + int x = tSprite.width () - 45; + tSprite.setTextColor ( WHITE ); + + tSprite.pushSprite ( X_ORIGIN, 0 ); // Write sprite to the display + + updateSidebar = false; +} // End of "DisplayInfo" + + +/* + * Draw the complete checkerboard + * This can be optimized if needed + */ + +void DrawFullCheckerBoard() +{ + for ( int p=0; p 0 ) + img.pushSprite( X_ORIGIN+p-1, Y_ORIGIN ); + } + +// Serial.println ( "DrawFullCheckerBoard" ); +} + + +/* + * "DrawCheckerBoard" - Paints the grid. It now uses a sprite so no need to + * erase the old grid, just draw a new one. + * + * The sprite is two pixels wide, and the image from the previous data point + * is scrolled and then new data point drawn. The data from last is scrolled + * so any line from before is retained. + */ + +void DrawCheckerBoard ( int x ) +{ + if ( x == 0 ) // "x" is the sweepStep, if zero + return; // then just return. + + if ( x == 1 ) + { + img.fillSprite ( BLACK ); // Clear the sprite + img.drawFastVLine ( 0, 0, GRID_HEIGHT, DARKGREY ); // Draw vertical line at edge + + for ( int y=0; y <= Y_GRID; y++ ) + img.drawPixel ( 1, y*DELTA_Y, DARKGREY ); // Draw the horizontal grid lines + } + + else + { + img.setScrollRect ( 0, 0, 2,GRID_HEIGHT, BLACK ); // Scroll the whole sprite + img.scroll ( -1, 0 ); + int lastStep = x - 1; + + if (( x % DELTA_X ) == 0 ) // Need a vertical line here + img.drawFastVLine ( 1, 0, GRID_HEIGHT, DARKGREY ); + + else + for ( int y = 0; y <= Y_GRID; y++ ) + img.drawPixel ( 1, y * DELTA_Y, DARKGREY ); // Draw the horizontal grid lines + } +} + + +/* + * Function to work out the y coordinate on img sprite for a given RSSI value + * Takes into account the display scaling, attenuation and level offset + */ + +uint16_t rssiToImgY ( uint8_t rSSI ) +{ + int delta = setting.MaxGrid - setting.MinGrid; + double y = rssiTodBm ( rSSI ); + + y = ( y - setting.MinGrid ) * GRID_HEIGHT / delta; + + if ( y >= GRID_HEIGHT ) + y = GRID_HEIGHT-1; + + if ( y < 0 ) + y = 0; + + return GRID_HEIGHT - 1 - (int) y; +} + + +/* + * Function to convert rSSi to dBm + */ + +double rssiTodBm ( uint8_t rSSI ) +{ + return (( rSSI / 2.0 + setting.Attenuate ) - 120.0 ) + setting.LevelOffset; +} + + +/* + * "DisplayPoint" - Display a point on the chart. + * + * The "img" sprite is 2 pixels wide to enable the last points to be also + * plotted to make lines look good. The img sprite has been scrolled in + * "DrawCheckerBoard". + */ + +void DisplayPoint ( uint8_t* data, int i, int color ) +{ + if ( i < 1 ) + return; + + int lastPoint = i - 1; + int delta = setting.MaxGrid - setting.MinGrid; + double f0 = (( data[i] / 2.0 + setting.Attenuate ) - 120.0 ) + setting.LevelOffset; // Current point + f0 = ( f0 - setting.MinGrid ) * GRID_HEIGHT / delta; + + if ( f0 >= GRID_HEIGHT ) + f0 = GRID_HEIGHT-1; + + if ( f0 < 0 ) + f0 = 0; + + double f1 = (( data[lastPoint] / 2.0 + setting.Attenuate ) - 120.0) + setting.LevelOffset; // Previous point + + f1 = ( f1 - setting.MinGrid ) * GRID_HEIGHT / delta; + + if ( f1 >= GRID_HEIGHT ) + f1 = GRID_HEIGHT-1; + + if ( f1 < 0 ) + f1 = 0; + + int Y0 = GRID_HEIGHT - 1 - (int) f0; + int Y1 = GRID_HEIGHT - 1 - (int) f1; + + img.drawLine ( 0, Y1, 1, Y0, color ); +} + + +/* + * "DisplayGainPoint" - Added by M0WID to display a scan of the gain setting. + */ + +void displayGainPoint ( unsigned char *data, int i, int color ) +{ + if ( i == 0 ) + return; + + int lastPoint = i - 1; + int delta = 50; // Scale of y axis + double f0 = ( data[i] ) ; + +// Serial.printf ( "gain %f \n", f ); + + f0 = ( f0 ) * GRID_HEIGHT / delta; + + if ( f0 >= GRID_HEIGHT ) + f0 = GRID_HEIGHT - 1; + + if ( f0 < 0 ) + f0 = 0; + + double f1 = ( data[lastPoint] ); + + f1 = ( f1 ) * GRID_HEIGHT / delta; + + if ( f1 >= GRID_HEIGHT ) + f1 = GRID_HEIGHT - 1; + + if ( f1 < 0 ) + f1 = 0; + + int Y0 = GRID_HEIGHT - 1 - (int) f0; + int Y1 = GRID_HEIGHT - 1 - (int) f1; + + img.drawLine ( 0, Y1, 1, Y0, color ); +} + + +/* + * "CreateGainScale" puts the decibel values for the gain trace into a sprite for + * display at the right side of the grid. + * + * NOTE Due to temporary error in TFT_eSPI library the inverted colour is used + * so TFT_BLUE actually results in TFT_GREEN! + */ + +void CreateGainScale () +{ + gainScaleSprite.deleteSprite(); + gainScaleSprite.setAttribute ( PSRAM_ENABLE, false ); + gainScaleSprite.setColorDepth ( 16 ); // Using 16 bit (RGB565) colors + gainScaleSprite.createSprite(CHAR_WIDTH * 2, GRID_HEIGHT); + gainScaleSprite.fillSprite(BLACK); + gainScaleSprite.setPivot( 0, 0 ); + gainScaleSprite.setTextSize ( 1 ); + gainScaleSprite.setTextColor ( TFT_BLUE ); // Draw text (transparent background) GAIN_COLOR + int w = gainScaleSprite.width(); + +// Serial.printf("Create gain scale - get sprite width %i \n", w); + + if ( w != CHAR_WIDTH * 2 ) + { + Serial.println ( "Error - no gain scale sprite" ); + return; + } + + for ( int i = 0; i < 5; i++ ) + { + gainScaleSprite.setCursor ( 0, 2 + ( DELTA_Y * i * 2 )); + gainScaleSprite.print ( 50 - ( i * 10 )); + } +} + +/* + * "ledcAnalogWrite" - Arduino like analogWrite used for PWM control of backlight. + * + * "value" has to be between 0 and valueMax + */ + +void ledcAnalogWrite ( uint8_t channel, uint32_t value, uint32_t valueMax = 255 ) +{ + if ( value > valueMax ) + value = valueMax; + + uint32_t duty = ( 8191 / valueMax ) * value; // Calculate duty, 8191 from 2 ^ 13 - 1 + ledcWrite ( channel, duty ); // write duty to LEDC +} + + +/* + * "setUpLEDC" - Sets up the LEDC PWM for the backlight. + * + * "TFT_BL" is defined in the User_setup.h file in the "TFT_eSPI" library + * If BACKLIGHT_LEVEL is not defined then your board uses a resistor instead + */ +#ifdef BACKLIGHT_LEVEL + +void setUpLEDC () +{ + ledcSetup ( LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT ); + ledcAttachPin ( TFT_LED, LEDC_CHANNEL_0 ); + ledcAnalogWrite ( LEDC_CHANNEL_0, BACKLIGHT_LEVEL ); +} + +#endif + +/* + * "TouchCalibrate" - Runs the touchscreen calibration sequence using the + * procedure in the "TFT_eSPI" library. + * + * This used to be called from "setup" by removing the comment from the + * function call. That can still be done, however, it is now also invoked + * from the "CONFIG" menu. + * + * The calibration constants are saved to the flash memory and recalled at + * startup, so it need only be done once. + */ + +void TouchCalibrate () +{ + uint8_t calDataOK = 0; + + tft.fillScreen ( BLACK ); + tft.setCursor ( 20, 0 ); + tft.setTextFont ( 2 ); + tft.setTextSize ( 1 ); + tft.setTextColor ( WHITE, BLACK ); + + tft.println ( "Touch corners as indicated" ); + + tft.setTextFont ( 1 ); + tft.println (); + + tft.calibrateTouch ( config.touch_cal, MAGENTA, BLACK, 15 ); + + Serial.print ( "\n\n// New calibration data: { " ); + + for ( uint8_t i = 0; i < 5; i++ ) + { + Serial.print ( config.touch_cal[i] ); + + if ( i < 4 ) + Serial.print ( ", " ); + } + + Serial.println ( " };\n\n" ); + + WriteConfig(); // Save the new calibration data to flash + + tft.fillScreen ( BLACK ); + tft.setTextColor ( GREEN, BLACK); + tft.println ( "Calibration complete!" ); + tft.println ( "Calibration values sent to Serialport\nand saved to flash." ); + + delay ( 3000 ); +} + + +/* + * Print out the files in the SPIFFS (SPI Fllat File System) + * This requires some files to be loaded into the ESP32's SPIFFS file system + * before the capability can be used. + * + * Conditionalized on whether or not WiFi is in use: + */ + +void printSPIFFS() +{ + char fileName[25]; + char fileSize[10]; + + Serial.println ( "Contents of SPIFFS:" ); + + fs::File root = SPIFFS.open ( "/" ); // Open the root object + fs::File file = root.openNextFile (); // Open the 1st file + + while ( file ) // Loop through the files + { + if ( file.isDirectory () ) // If the file is a directory + { + strcpy ( fileName, file.name () ); + Serial.printf ( " DIR : %-s\n", fileName ); + } + + else // File is a regular file + { + strcpy ( fileName, file.name() ); + itoa ( file.size(), fileSize, 10 ); + Serial.printf ( " FILE: %-25s%10s\n", fileName, fileSize ); + } + + file = root.openNextFile(); // Move to the next file + } +} diff --git a/simpleSA_wifi.cpp b/simpleSA_wifi.cpp new file mode 100644 index 0000000..3a9faa5 --- /dev/null +++ b/simpleSA_wifi.cpp @@ -0,0 +1,866 @@ +/* + * "TinySA_wifi.cpp" + * + * Trial wifi charting functionality to see if WiFi affects the scan results + * Based on example here https://circuits4you.com/2019/01/11/esp8266-data-logging-with-real-time-graphs/ + * + * Requires some files to be placed into the spiffs area of flash + * + * Modified in Version 2.1 by WA2FZW: + * + * Eliminated all calls to "WriteSettings". All of the functions in "Cmd.cpp" + * that actually update the "setting" structure elements now handle that task + * and do so based on whether or not the value of the parameter actually + * changed or not which wasn't the case before. + */ + + +#include "TinySA_wifi.h" // WiFi definitions +#include "Si4432.h" // Si4432 definitions +#include "Cmd.h" // Command processing functions + +extern int bpfCount; // Number of elements in the bandpassFilters array +extern int updateSidebar; // Flag to indicate no of clients has changed +extern void ClearDisplay (); +extern void DisplayError ( uint8_t severity, const char *l1, const char *l2, const char *l3, const char *l4 ); + + +/* + * In Version 1.8, the transmitter and receiver Si4432 modules are implemented as + * objects. + */ + +extern Si4432 rcvr; // Object for the receiver +extern Si4432 xmit; // And the transmitter + +extern bool AGC_On; // Flag indicates if Preamp AGC is enabled +extern uint8_t AGC_Reg; // Fixed value for preampGain if not auto + +extern uint32_t startFreq_IF; +extern uint32_t stopFreq_IF; +extern uint32_t sigFreq_IF; + + +AsyncWebServer server ( 80 ); // Create the webserver object +AsyncResponseStream *response; + + +/* + * Install WebSockets library by Markus Sattler + * https://github.com/Links2004/arduinoWebSockets + */ + +WebSocketsServer webSocket = WebSocketsServer ( 81 ); + +uint8_t socketNumber; +unsigned long messageNumber; + +extern uint8_t numberOfWebsocketClients; + +/* + * Tracking of number of Wi-Fi reconnects and total connection time + */ + +unsigned long numberOfReconnects; +unsigned long millisConnected; + +IPAddress ipAddress; // Store the IP address for use elsewhere, eg info + + +/* + * Function to format IP address nicely + */ + +char *FormatIPAddress ( IPAddress ipAddress ) +{ + static char formatBuffer[20] = {0}; + sprintf( formatBuffer, "%d.%d.%d.%d", ipAddress[0], ipAddress[1], + ipAddress[2], ipAddress[3] ); + return formatBuffer; +} + + +boolean startAP () // Start the WiFi Access Point, keep the user informed. +{ + ClearDisplay (); // Fade to black + + Serial.println ( "Starting Access Point" ); // Put in the instructions + + +/* + * Start by kicking off the soft-AP. Call depends on whether or not password + * is required to connect + */ + +#ifdef AP_PASSWORD + + boolean result = WiFi.softAP ( PROGRAM_NAME, AP_PASSWORD ); + +#else + + boolean result = WiFi.softAP ( PROGRAM_NAME ); + +#endif + + if ( !result ) // This has failed, tell the user + DisplayError ( ERR_WARN, "Failed to open AP:", "WiFi Disabled", NULL, NULL ); + + else + ipAddress = WiFi.localIP (); + + Serial.printf ( "Access Point started, result = %b \n", result ); + return result; +} + + +boolean connectWiFi() // Connect to Wifi using SSID (ie via Router) +{ + + Serial.printf ( "Connecting to: %s \n", WIFI_SSID ); +// Serial.println( (char *) &configuration.WiFi_SSID[0] ); + + WiFi.softAPdisconnect (); // Disconnect anything that we may have + + +/* + * Start the connection: + */ + +// WiFi.begin ( (char *) &configuration.WiFi_SSID[0], (char *) &configuration.WiFi_Password[0] ); + WiFi.begin ( WIFI_SSID, WIFI_PASSWORD ); + + WiFi.setSleep (false ); // Disable sleep + + +/* + * Apply any hostname that we may have + */ + +// if ( strlen ( (char *) &configuration.Hostname[0]) > 0 ) +// WiFi.setHostname ( (char *) &configuration.Hostname[0] ); +// else + WiFi.setHostname ( PROGRAM_NAME ); + + int maxTry = 10; // Wait for the connection to be made. + + tft.setCursor ( 0, 190 ); + tft.printf ( "Connecting to WiFi %s", WIFI_SSID ); + + while (( WiFi.status() != WL_CONNECTED ) && ( maxTry > 0 )) + { + tft.print("."); // Nice touch!!! + delay ( 1000 ); // Wait and update the try count. + maxTry--; + + if ( maxTry <= 0 ) + { + DisplayError ( ERR_WARN, "Connecting to", WIFI_SSID, "failed!", "WiFi Disabled" ); + return false; + } + } + + + ipAddress = WiFi.localIP (); // We are connected, display the IP address + + Serial.printf ( "Connected - IP %s \n", FormatIPAddress ( ipAddress )); + tft.printf ( "\n\nConnected - IP %s \n", FormatIPAddress ( ipAddress )); +} + + +/* + * Handle websocket events + * This is how dta is sent from the web page to the ESP32 + */ + +void webSocketEvent ( uint8_t num, WStype_t type, uint8_t* payload, size_t payloadLength ) +{ + switch ( type ) + { + case WStype_DISCONNECTED: + Serial.printf("[%u] Disconnected!\n", num); + if ( numberOfWebsocketClients > 0 ) + numberOfWebsocketClients--; + updateSidebar = true; + break; + + case WStype_CONNECTED: + Serial.printf ( "[%u] Connected from ", num ); + Serial.println ( webSocket.remoteIP ( num )); + numberOfWebsocketClients++; + updateSidebar = true; + webSocket.sendTXT ( num, "Connected" ); // send message back to client + break; + + +/* + * Format of message to process + * + * #a 123.123 Start frequency + * #b 123.123 Stop frequency + * + * Other formats are ignored + */ + + case WStype_TEXT: + if ( payload[0] == '#' ) + { + if ( payloadLength > 3 ) + { + char* field = strtok ( (char*) &payload[3], " " ); // Extract the field value as string + float value = atof ( field ); // Convert to float + + if ( isnan ( value )) // If not a number + return; // Bail out! + + Serial.printf ( "payload command %c value %f\n", payload[1], value ); + + switch ( payload[1] ) + { + case 'a': + SetSweepStart ( value * 1000000.0 ); // Set sweep start frequency + break; + + case 'b': + SetSweepStop ( value * 1000000.0 ); // Set sweep stop frequency + break; + + case 'c': + SetSweepCenter ( value * 1000000.0, WIDE ); // Set sweep center frequency + break; + + case 'd': + SetLoDrive ( (uint8_t) value ); + break; + + case 'g': + SetPreampGain( (int) value ); // Set PreAmp gain register + break; + + case 'i': // IF Frequency + SetIFFrequency ( (int32_t) ( value * 1000000.0 )); + break; + + case 'o': // Ref Output (LO GPIO2) + SetRefOutput ( (int) value ); + break; + + case 'p': // Adjust actual power level + RequestSetPowerLevel( value ); + break; + + case 's': // Adjust sweep span + SetSweepSpan ( (int32_t) ( value * 1000000.0 )); + break; + + case 'A': // Internal Attenuation (PE4302) + SetAttenuation ( value ); + break; + + case 'R': // Requested RBW. 0=Auto + SetRBW ( value ); + break; + + case 'S': // Spur Reduction on/off + SetSpur ( value ); + break; + + default: + Serial.printf ( "payload[1] was %c\n", payload[1] ); + break; + } + } + } + + else // payload[0] is not '#' + { + webSocket.sendTXT ( num, "pong" ); // send message back to client to keep connection alive + Serial.printf ( "[%u] get Text: %s\n", num, payload ); + } + + break; + + default: + Serial.println ( "Case?" ); + break; + } +} + + +/* + * Monitor Wi-Fi connection if it is alive. If not alive then wait until it reconnects. + */ + +void isWiFiAlive ( void ) +{ + if ( WiFi.status() != WL_CONNECTED ) + { + Serial.print ( "not connected" ); + + while ( WiFi.status() != WL_CONNECTED ) + { + Serial.print ( "." ); + delay(500); + } + + numberOfReconnects++; + millisConnected = millis(); + } +} + + +/* + * Some routines for XML + */ + +char *escapeXML ( char *s ) // Use special codes for some characters +{ + static char b[1024]; + + b[0] = '\0'; // Null terminator + + for ( int i = 0; i < strlen(s); i++ ) + { + switch ( s[i] ) + { + case '\"': + strcat ( b,""" ); + break; + + case '&': + strcat (b, "&" ); + break; + + case '<': + strcat ( b,"<" ); + break; + + case '>': + strcat ( b,">" ); + break; + + default: + int l = strlen ( b ); + b[l] = s[i]; + b[l + 1] = '\0'; + break; + } + } +} + + +/* + * Add an XML tag with its value to the buffer b + */ + +void addTagNameValue ( char *b, char *_name, char *value ) +{ + strcat ( b,_name ); + strcat ( b, "=\"" ); + strcat ( b,value ); + strcat ( b,"\" " ); +} + + +/* + * On request from web page convert the data from a scan into XML and send + * + * Ideally we would push the data to the web page at the end of a scan, + * or perhaps just create the xml at the end of each scan - investigate later + */ + +void onGetScan ( AsyncWebServerRequest *request ) +{ + response = request->beginResponseStream ( "text/xml" ); +// Serial.println ( "onGetScan" ); + + response->print ( "" ); + response->println ( "" ); + + for( int i = 0; i < DISPLAY_POINTS-1; i++ ) // For each data point + { +// Serial.printf ( " %i\n",myFreq[i], myData[i], i ); + response->printf ( "

\n", myFreq[i], myData[i] ); + } + + response->print ( "" ); + 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 ( "" ); + response->printf ( "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, "" ); // 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,"" ); + Serial.println ( "... " + WiFi.SSID (i) ); + } + WiFi.scanDelete (); + if ( WiFi.scanComplete() == -2 ) + WiFi.scanNetworks ( true ); + } + strcat ( b, "" ); // 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 (); +} diff --git a/simpleSA_wifi.h b/simpleSA_wifi.h new file mode 100644 index 0000000..0bcd3d4 --- /dev/null +++ b/simpleSA_wifi.h @@ -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 library +#include +#include "ESPAsyncWebServer.h" // ESP32 Webserver library +#include "SPIFFS.h" // ESP32 File system +#include // Display library + +#include +#include // Install using Library Manager or go to arduinojson.org + + +/* + * Install WebSocketes library by Markus Sattler + * https://github.com/Links2004/arduinoWebSockets + */ + +#include + +#include +#include +#include + +#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 diff --git a/ui.cpp b/ui.cpp new file mode 100644 index 0000000..d3137e7 --- /dev/null +++ b/ui.cpp @@ -0,0 +1,3196 @@ +/* + * "ui.cpp" + * + * This file implements the touch screen interface for the TinySA. + * + * Modifications through Version 2.5: + * + * Lots of general cleanup; removed a lot of the menu processing that was left + * over from the NanoVNA code but not used here including the "marker" and + * "calibration" stuff. For now some NanoVNA holdovers are left intact as they + * may be used in the future such as multiple "traces". + * + * + * Modifications in Version 2.6 by WA2FZW: + * + * Replaced the use of macros to define the menu items and replaced those with + * a class/object implementation; much neater! + * + * + * Modifications in Version 2.7 by WA2FZW: + * + * Eliminated the "kpf" array which contained the characters used to paint the numbers + * in the keypad keys and in the numerical input box. The "kpf" array is replaced with + * the "KP_Font" array, which contains bitmaps for the characters that make for a much + * better looking display! I also eliminated all the characters leftover from the + * NanoVNA code that aren't used here. + */ + + +#include // SPI Bus handling library +#include "tinySA.h" +#include "ui.h" // User interface definitions and prototypes +#include "Cmd.h" // Command processing functions +#include "preferences.h" // Save/recall functions +#include // Display/Touch Screen header file +#include "Menu.h" // "Menuitem" class definition +#include "Marker.h" // Marker class definition +#include "Si4432.h" // Si4432 class definition + + +extern Marker marker[MARKER_COUNT]; // Array of markers + +extern void TouchCalibrate (); // Function is in "tinySA.ino" +extern char* FormatIPAddress (IPAddress ipAddress ); // Function in "TinySA_wifi.cpp" + +extern IPAddress ipAddress; // Global in "tinySA.ino" +extern TFT_eSPI tft; // TFT Screen object + +//extern void RedrawHisto (); +extern void menuExit(); + +extern void ClearDisplay ( void ); +extern void DisplayError ( uint8_t severity, const char *l1, const char *l2, const char *l3, const char *l4 ); +extern void SetRX ( int ); +extern void setMode (uint16_t newMode); + +extern int changedSetting; // Display needs updated + +extern bool AGC_On; // Flag indicates if Preamp AGC is enabled +extern uint8_t AGC_Reg; // Fixed value for preampGain if not auto + +extern Si4432 rcvr; // Si4432 Receiver object +extern Si4432 xmit; // Si4432 Transmitter object +#ifdef SI_TG_IF_CS +extern Si4432 tg_if; // Si4432 Tracking Generator IF +#endif +#ifdef SI_TG_LO_CS +extern Si4432 tg_lo; // Si4432 Tracking Generator LO +#endif + +extern int VFO; // Selects transmitter or receiver Si4432 + + +/* + * This might seem a bit weird, but I've had to do something like this in other projects! + * Sooner or later I'll figure out ir normal "true" and "false" can be used instead. As + * these are used in some of the logic that has to do with multiple traces which are not + * implemented (yet), it's impossible to tell if they can be replaced by the normal + * indicators. + */ + +#define FALSE 0 +#define TRUE -1 + + +/* + * The "uistat" structure seems to contain information about the current state + * of the user interface, but I haven't figured out what the elements all mean + * yet. + */ + +typedef struct { + int8_t digit; // Original comment said "0 to 5", but it gets initialized + // to '6' below! + int8_t digit_mode; + int8_t current_trace; // 0 to 3 ??? + int32_t value; // For editing at numeric input area + int32_t previous_value; +} uistat_t; + + +/* + * Set the initial user interface status: + */ + +uistat_t uistat = { + 6, // digit - See note above + 0, // digit_mode + 0, // current_trace + 0, // value + 0 // previous_value +}; + + +/* + * Create the "config" structure; not sure why in here and not in the main program file. + */ + +config_t config; + + +/* + * Define the trace type structure. This is a NanoVNA holdover and needs to be eliminated, + * but for now, there are a lot of places where it is used. I'll get to it eventually! + */ + +typedef struct +{ + uint8_t enabled; + uint8_t type; + uint8_t channel; + uint8_t polar; + float scale; + float refpos; +} trace_t; + + +/* + * Then create an array of trace types (Note, "TRACE_COUNT" is currently defined as '1' + * in the "tinySA.h" file. + */ + +trace_t trace[TRACE_COUNT]; + + +/* + * These definitions are related to manipulation of the menu on the touch screen: + */ + +#define NO_EVENT 0 +#define EVT_BUTTON_SINGLE_CLICK 0x01 +#define EVT_BUTTON_DOUBLE_CLICK 0x02 +#define EVT_BUTTON_DOWN_LONG 0x04 +#define EVT_UP 0x10 +#define EVT_DOWN 0x20 +#define EVT_REPEAT 0x40 + + +/* + * Timing for touch screen button events: + */ + +#define BUTTON_DOWN_LONG_TICKS 5000 // Original comment said 1 Second? +#define BUTTON_DOUBLE_TICKS 5000 // 500 mS +#define BUTTON_REPEAT_TICKS 1000 // 100 mS +#define BUTTON_DEBOUNCE_TICKS 200 // 20 mS + + +/* + * These look like timer values, but they aren't used anywhere; the code compiles + * fine with them commented out: + */ + +static uint16_t last_button = 0; +static uint32_t last_button_down_ticks; +static uint32_t last_button_repeat_ticks; + +static int8_t inhibit_until_release = FALSE; + + +/* + * Requested operations: + */ + +enum { OP_NONE = 0, OP_TOUCH, OP_FREQCHANGE }; + +uint8_t operation_requested = OP_NONE; // No operations so far + + +/* + * These define the things that are entered using the keypad. The numbers are used as an + * index into the "keypad_mode_label" array which contains the strings that appear at + * the left of the numerical entry box and the "keypads_mode_tbl" in the "menu_invoke" + * function. + */ + +enum { KM_START, KM_STOP, KM_CENTER, KM_SPAN, KM_FOCUS, + KM_REFPOS, KM_ATTENUATION, KM_ACTUALPOWER, KM_IFFREQ, KM_PREAMP, + KM_TUNE, KM_SGFREQ, KM_SGLEVEL, KM_SGLEVCAL, KM_IFSTART, KM_IFSTOP, + KM_IFSIG, KM_TGOFFSET, KM_TGLO_DRIVE, KM_TGIF_DRIVE, KM_BANDSCOPESTART, KM_BANDSCOPESPAN }; + + +/* + * These are the strings that appear in the numerical entry box under the keypad. + * + * The order of the labels corresponds to the "KM_" list above. + * + * WA2FZW - need to change the formatting as on some menus, the numerical entry + * box covers the "<- BACK" button. + */ + +static const char * const keypad_mode_label[] = +{ + "START", "STOP", "CENTER", "SPAN", "FOCUS", + "REFPOS", "ATTEN", "POWER", "IF FREQ", "PREAMP", + "XTAL CAL", "SG FREQ", "dBm", "Max dBm", "IF START", "IF STOP", + "IF Sig Freq", "TG OFFSET", "TG LO Drive", "TG IF Drive", "START", "SPAN" +}; + + +uint8_t ui_mode = UI_NORMAL; // User interface in "NORMAL" mode for now +static uint8_t keypad_mode; // Current keypad mode +static int8_t selection = 0; // Current menu selection, I think + +static int8_t last_touch_status = FALSE; + +static int16_t last_touch_x; +static int16_t last_touch_y; + +#define EVT_TOUCH_NONE 0 // Touch screen status condition +#define EVT_TOUCH_DOWN 1 // definitions +#define EVT_TOUCH_PRESSED 2 +#define EVT_TOUCH_RELEASED 3 + +#define NUMINPUT_LEN 10 // Length of the mumeric input box + +#define KP_CONTINUE 0 // Keypad entry status definitions +#define KP_DONE 1 +#define KP_CANCEL 2 + +static char kp_buf[NUMINPUT_LEN+1]; // Buffer for numerical entry +static int8_t kp_index = 0; // Index to the above buffer + +static uint8_t selectedMarker = 0; // Currently selected marker (default is 1st one) + +static uint16_t bg = BLACK; // Background is normally black + + +/* + * The function prototypes are needed here as many of the functions are referenced in + * setting up the menu structure which is done outside the context of any of the + * actual program code. + */ + +static void ui_mode_normal ( void ); +static void ui_mode_menu ( void ); +static void ui_mode_numeric ( int _keypad_mode ); +static void ui_mode_keypad ( int _keypad_mode ); +static void draw_menu ( void ); +static void leave_ui_mode ( void ); +static void erase_menu_buttons ( void ); +static void ui_process_keypad ( void ); +static void menu_push_submenu ( Menuitem *submenu ); +static void menu_move_back ( void ); +static void menu_mode_cb ( int item ); // M0WID added 3.0d +static void menu_save_cb ( int item ); +static void menu_refer_cb ( int item ); +static void menu_refer_cb2 ( int item ); +static void menu_trace_cb ( int item ); // WA2FZW - Repurposed in Version 2.6 +static void menu_format2_cb ( int item ); +static void menu_format_cb ( int item ); +static void menu_scale_cb ( int item ); +static void menu_sweep_cb ( int item ); +static void menu_IF_sweep_cb ( int item ); // M0WID added 3.0c +static void menu_recall_cb ( int item ); +static void menu_version_cb (int item ); +static void menu_generate_cb(int item); // WA2FZW - Added in M0WID's Version 05 +static void menu_Bandscope_cb ( int item ); // M0WID added 3.0f + +static void menu_tracking_cb(int item); // M0WID - added in 3.0e +static void menu_tg_offset_cb(int item); // M0WID - added in 3.0e +static void menu_tgIF_drive_cb(int item); // M0WID - added in 3.0e +static void menu_tgLO_drive_cb(int item); // M0WID - added in 3.0e + +static void menu_rbw_cb ( int item ); +static void menu_dBper_cb ( int item ); +static void menu_dBgain_cb (int item ); // M0WID +static void menu_autosettings_cb ( int item ); +static void menu_average_cb (int item ); +static void menu_spur_cb ( int item ); +static void menu_actualpower_cb ( int item ); +static void menu_storage_cb ( int item ); +static void menu_IF_freq_cb ( int item ); // WA2FZW - Added in Version 2.5 +static void menu_sig_freq_cb ( int item ); // M0WID - Added in Version 3.0a +static void menu_sig_levCal_cb ( int item ); // M0WID - Added in Version 3.0a +static void menu_sig_level_cb ( int item ); // M0WID - Added in Version 3.0b +static void menu_atten_cb ( int item ); // WA2FZW - Added in Version 2.6 +static void menu_touch_cb ( int item ); // WA2FZW - Added in Version 2.6 +static void KeyNumber ( int8_t key, int x, int y ); // WA2FZW - Added in Version 2.7 + +static void menu_tune_cb ( int item ); // WA2FZW - Added in Version 2.7 +static void menu_save_config_cb ( int item ); // WA2FZW - Added in Version 2.7 +static void menu_markers_cb ( int item ); // WA2FZW - Added in Version 2.7 +static void menu_marker_select_cb ( int item ); // WA2FZW - Added in Version 2.9 +static void menu_marker_color_cb ( int item ); // WA2FZW - Added in Version 2.9 + +static void StartMarkerMenu ( void ); // M0WID - push menu stack to start at marker level not root +static void StartSweepMenu ( void ); // M0WID - push menu stack to start at sweep level not root +static void StartDisplayMenu ( void ); // M0WID - push menu stack to start at display level not root +static void StartRBWMenu ( void ); // M0WID - push menu stack to start at rbw level not root + +/* + * The "KN_xxx" definitions (for key number) replace the "KP_xxx" definitions + * previously used to paint the numbers on the keys and in the numerical entry + * box. I eliminated all the leftovers from the NanoVNA code that aren't used + * here. + */ + + #define KN_PERIOD 10 // '.' + #define KN_MINUS 11 // '-' + #define KN_X 12 // 'X' (aka "Enter") + #define KN_K 13 // 'K' (for "Kilo") + #define KN_M 14 // 'M' (for "Mega") + #define KN_BS 15 // Backspace arrow + + +/* + * The "KP_Font" array contains the bitmaps for the characters used to paint the keypas + * keys and the numerical entry box. The array is indexed by the "KN_xxx" definitions + * above, + */ + +uint16_t KP_Font[] = { + + 0x0FF0, 0x3FFC, 0x7FFE, 0x7C3E, 0xF83F, 0xF07F, 0xF07F, // '0' => index = 0 + 0xF0FF, 0xF0FF, 0xF1EF, 0xF1EF, 0xF3CF, 0xF3CF, 0xF78F, + 0xF78F, 0xFF0F, 0xFF0F, 0xFE1F, 0x7E3E, 0x7FFE, 0x3FFC, + 0x0FF0, + + 0x00F0, 0x01F0, 0x03F0, 0x07F0, 0x0FF0, 0x0FF0, 0x0EF0, // '1' => index = 1 + 0x0CF0, 0x00F0, 0x00F0, 0x00F0, 0x00F0, 0x00F0, 0x00F0, + 0x00F0, 0x00F0, 0x00F0, 0x00F0, 0x00F0, 0x03FC, 0x03FC, + 0x03FC, + + 0x0FF0, 0x3FFC, 0x7FFE, 0x7C3E, 0xF81F, 0xF00F, 0xF00F, // '2' => index = 2 + 0x001F, 0x003F, 0x007E, 0x00FC, 0x01F8, 0x03F0, 0x07E0, + 0x0FC0, 0x1F80, 0x3F00, 0x7E00, 0xFC00, 0xFFFF, 0xFFFF, + 0xFFFF, + + 0x0FF0, 0x3FFC, 0x7FFE, 0x7C3E, 0xF81F, 0xF00F, 0xF00F, // '3' => index = 3 + 0x001E, 0x003E, 0x01FC, 0x01F8, 0x01FC, 0x007E, 0x001F, + 0x000F, 0xF00F, 0xF00F, 0xF81F, 0x7C3E, 0x7FFE, 0x3FFC, + 0x0FF0, + + 0x01F8, 0x01F8, 0x03F8, 0x03F8, 0x07F8, 0x07F8, 0x0FF8, // '4' => index = 4 + 0x0F78, 0x1F78, 0x1E78, 0x3E78, 0x3C78, 0x7C78, 0x7878, + 0xF878, 0xFFFF, 0xFFFF, 0xFFFF, 0x0078, 0x0078, 0x0078, + 0x0078, + + 0xFFFF, 0xFFFF, 0xFFFF, 0xF000, 0xF000, 0xF000, 0xF000, // '5' => index = 5 + 0xF7F0, 0xFFFC, 0xFFFE, 0xFC3E, 0xF81F, 0x000F, 0x000F, + 0x000F, 0x000F, 0xF00F, 0xF81F, 0x7C3E, 0x7FFE, 0x3FFC, + 0x0FF0, + + 0x0FF0, 0x3FFC, 0x7FFE, 0x7C3E, 0xF81F, 0xF00F, 0xF000, // '6' => index = 6 + 0xF000, 0xF7F0, 0xFFFC, 0xFFFE, 0xFC3E, 0xF81F, 0xF00F, + 0xF00F, 0xF00F, 0xF00F, 0xF81F, 0x7C3E, 0x7FFE, 0x3FFC, + 0x0FF0, + + 0xFFFF, 0xFFFF, 0xFFFF, 0x001F, 0x003E, 0x007C, 0x00F8, // '7' => index = 7 + 0x01F0, 0x01E0, 0x03E0, 0x03C0, 0x07C0, 0x0780, 0x0780, + 0x0F80, 0x0F00, 0x0F00, 0x0F00, 0x0F00, 0x0F00, 0x0F00, + 0x0F00, + + 0x07E0, 0x1FF8, 0x3FFC, 0x7C3E, 0x781E, 0x781E, 0x781E, // '8' => index = 8 + 0x3C3C, 0x1FF8, 0x0FF0, 0x3FFC, 0x7C3E, 0x781E, 0xF00F, + 0xF00F, 0xF00F, 0xF00F, 0xF81F, 0x7C3E, 0x7FFE, 0x3FFC, + 0x0FF0, + + 0x0FF0, 0x3FFC, 0x7FFE, 0x7C3E, 0xF81F, 0xF00F, 0xF00F, // '9' => index = 9 + 0xF00F, 0xF00F, 0xF81F, 0x7C3F, 0x7FFF, 0x3FFF, 0x0FFF, + 0x000F, 0x000F, 0xF00F, 0xF81F, 0x7C3E, 0x7FFE, 0x3FFC, + 0x0FF0, + + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // '.' => index = KN_PERIOD + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0380, 0x07C0, 0x07C0, 0x0380, + 0x0000, + + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // '-' => index = KN_MINUS + 0x0000, 0x0000, 0x0000, 0x3FFC, 0x3FFC, 0x3FFC, 0x3FFC, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, + + 0x0000, 0x0000, 0xF00F, 0x781E, 0x781E, 0x3C3C, 0x3C3C, // 'X' => index = KN_X + 0x1E78, 0x1E78, 0x0FF0, 0x07E0, 0x0FF0, 0x0FF0, 0x1E78, + 0x1E78, 0x3C3C, 0x3C3C, 0x781E, 0x781E, 0xF00F, 0x0000, + 0x0000, + + 0xF01F, 0xF03E, 0xF07C, 0xF0F8, 0xF1F0, 0xF3E0, 0xF7C2, // 'K' => index = KN_K + 0xFF80, 0xFF00, 0xFE00, 0xFC00, 0xFC00, 0xFE00, 0xFF00, + 0xFFC0, 0xF7E0, 0xF3F0, 0xF1F0, 0xF0F8, 0xF07E, 0xF03F, + 0xF01F, + + 0xF00F, 0xF00F, 0xF81F, 0xF81F, 0xFC3F, 0xFC3F, 0xFE7F, // 'M' => index = KN_M + 0xFE7F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7EF, 0xF7EF, + 0xF3CF, 0xF3CF, 0xF18F, 0xF18F, 0xF00F, 0xF00F, 0xF00F, + 0xF00F, + + 0x0000, 0x0000, 0x0040, 0x00C0, 0x01C0, 0x03C0, 0x07C0, // Backspace arrow => index = KN_BS + 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, 0x7FFF, 0x3FFF, + 0x1FFF, 0x0FFF, 0x07C0, 0x03C0, 0x01C0, 0x00C0, 0x0040, + 0x0000 +}; + + +/* + * Define the actual menu item text and associated callback functions for all the menus. + * + * Modified in Version 2.6 by WA2FZW: + * + * Replaced all the "MENUITEM_xxxx" macros with the "Menuitem" class/object implementation. + * This not only affects the menu definitions, but how things are handled throughout the + * module. + * + * Note, that while it would be nice to have the main menu at the top of the list followed + * by any sub-menus, the compiler doesn't like that. The sub-menus have to be defined before + * they are referenced in a higher level menu. This was also the case with the original + * macro implementation. + */ + +static Menuitem menu_mode[] = // Select mode menu +{ + Menuitem ( MT_FUNC, "\2SWEEP\0LOW", menu_mode_cb ), + Menuitem ( MT_FUNC, "\2SIG\0GEN", menu_mode_cb ), + Menuitem ( MT_FUNC, "\2IF\0SWEEP", menu_mode_cb ), + Menuitem ( MT_FUNC, "\2BAND\0SCOPE",menu_mode_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + + +static Menuitem menu_average[] = // Enables averaging and sets value +{ + Menuitem ( MT_FUNC, "OFF", menu_average_cb ), // No averaging + Menuitem ( MT_FUNC, "MIN", menu_average_cb ), // Not sure about + Menuitem ( MT_FUNC, "MAX", menu_average_cb ), // These two + Menuitem ( MT_FUNC, " 2 ", menu_average_cb ), // Divide by 2 + Menuitem ( MT_FUNC, " 4 ", menu_average_cb ), // By 4 + Menuitem ( MT_FUNC, " 8 ", menu_average_cb ), // By 8 + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_storage[] = // Sweep storage options +{ + Menuitem ( MT_FUNC, "STORE", menu_storage_cb ), // Save current sweep + Menuitem ( MT_FUNC, "CLEAR", menu_storage_cb ), // Erase saved sweep + Menuitem ( MT_FUNC, "SUBTRACT", menu_storage_cb ), // Not sure what this does! + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_generate[] = // Frequency generator mode +{ +// Menuitem ( MT_FUNC, "OFF", menu_generate_cb ), + Menuitem ( MT_FUNC, "ON", menu_generate_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + + +static Menuitem menu_tracking[] = // Tracking generator mode +{ + Menuitem ( MT_FUNC, "OFF", menu_tracking_cb ), + Menuitem ( MT_FUNC, "ON", menu_tracking_cb ), + Menuitem ( MT_FUNC, "OFFSET", menu_tg_offset_cb ), + Menuitem ( MT_FUNC, "\2IF\0DRIVE", menu_tgIF_drive_cb ), +#ifdef SI_TG_LO_CS + Menuitem ( MT_FUNC, "\2LO\0DRIVE", menu_tgLO_drive_cb ), +#endif + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + + +static Menuitem menu_spur[] = // Turn spurious suppression on or off +{ + Menuitem ( MT_FUNC, "OFF", menu_spur_cb ), + Menuitem ( MT_FUNC, "ON", menu_spur_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_rbw[] = // Resolution bandwidth settings +{ + Menuitem ( MT_FUNC, " AUTO", menu_rbw_cb ), // In auto mode, there are many + Menuitem ( MT_FUNC, " 3kHz", menu_rbw_cb ), // more available settings that + Menuitem ( MT_FUNC, " 10kHz", menu_rbw_cb ), // are roughly the sweep range + Menuitem ( MT_FUNC, " 30kHz", menu_rbw_cb ), // divided by 300. + Menuitem ( MT_FUNC, "100kHz", menu_rbw_cb ), + Menuitem ( MT_FUNC, "300kHz", menu_rbw_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_dBper[] = // Scale setting menu +{ + Menuitem ( MT_FUNC, " 1dB/", menu_dBper_cb ), + Menuitem ( MT_FUNC, " 2dB/", menu_dBper_cb ), + Menuitem ( MT_FUNC, " 5dB/", menu_dBper_cb ), + Menuitem ( MT_FUNC, " 10dB/", menu_dBper_cb ), + Menuitem ( MT_FUNC, " 20dB/", menu_dBper_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +/* + * Menu to set the gain overide register (REG_AGCOR 0x69) in the SI4432 transceiver. + * See app note AN440 for the full explanation. + * + * Modified in Version 2.6 by WA2FZW: + * + * The original code had set values in a sub-menu. Now we use the keypad to set + * any value one likes!. But here are rules! See the documentation for the + * explanation. + */ + +static Menuitem menu_dBgain[] = +{ + Menuitem ( MT_FUNC, "AGC ON", menu_dBgain_cb ), //0x60 + Menuitem ( MT_FUNC, "\2SET\0VALUE", menu_dBgain_cb ), //Set actual value + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + + +static Menuitem menu_save[] = // Setting save options +{ + Menuitem ( MT_FUNC, "SAVE 0", menu_save_cb ), // This works like the NanoVNA + Menuitem ( MT_FUNC, "SAVE 1", menu_save_cb ), // where you an save a number of + Menuitem ( MT_FUNC, "SAVE 2", menu_save_cb ), // different configurations + Menuitem ( MT_FUNC, "SAVE 3", menu_save_cb ), + Menuitem ( MT_FUNC, "SAVE 4", menu_save_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_recall[] = // For recalling saved configurations +{ + Menuitem ( MT_FUNC, "RECALL 0", menu_recall_cb ), + Menuitem ( MT_FUNC, "RECALL 1", menu_recall_cb ), + Menuitem ( MT_FUNC, "RECALL 2", menu_recall_cb ), + Menuitem ( MT_FUNC, "RECALL 3", menu_recall_cb ), + Menuitem ( MT_FUNC, "RECALL 4", menu_recall_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_save_recall[] = // Save or recall options +{ + Menuitem ( MT_MENU, "SAVE", menu_save ), // Show save options + Menuitem ( MT_MENU, "RECALL", menu_recall ), // Show recall options + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + + +int menu_refer_value[] = { -1, 0, 1, 2, 3, 4, 5, 6 }; // Actual values for the register + +static Menuitem menu_refer2[] = // Part 2 of the reference values +{ + Menuitem ( MT_FUNC, "3MHz", menu_refer_cb2 ), + Menuitem ( MT_FUNC, "2MHz", menu_refer_cb2 ), + Menuitem ( MT_FUNC, "1MHz", menu_refer_cb2 ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_refer[] = // GPIO2 reference frequency values +{ + Menuitem ( MT_FUNC, "OFF", menu_refer_cb ), + Menuitem ( MT_FUNC, "30MHz", menu_refer_cb ), + Menuitem ( MT_FUNC, "15MHz", menu_refer_cb ), + Menuitem ( MT_FUNC, "10MHz", menu_refer_cb ), + Menuitem ( MT_FUNC, "4MHz", menu_refer_cb ), + Menuitem ( MT_MENU, " MORE->", menu_refer2 ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_traces[] = // Toggle display traces on and off +{ + Menuitem ( MT_FUNC, "\2dB\0On/Off", menu_trace_cb ), + Menuitem ( MT_FUNC, "\2GAIN\0On/Off", menu_trace_cb ), + Menuitem ( MT_MENU, "AVERAGE", menu_average ), // The sub menu allows values to be set + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_select_marker[] = +{ + Menuitem ( MT_FUNC, "MKR #1", menu_marker_select_cb ), + Menuitem ( MT_FUNC, "MKR #2", menu_marker_select_cb ), + Menuitem ( MT_FUNC, "MKR #3", menu_marker_select_cb ), + Menuitem ( MT_FUNC, "MKR #4", menu_marker_select_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_marker_color[] = +{ + Menuitem ( MT_FUNC, "WHITE", menu_marker_color_cb ), + Menuitem ( MT_FUNC, "RED", menu_marker_color_cb ), + Menuitem ( MT_FUNC, "BLUE", menu_marker_color_cb ), + Menuitem ( MT_FUNC, "GREEN", menu_marker_color_cb ), + Menuitem ( MT_FUNC, "YELLOW", menu_marker_color_cb ), + Menuitem ( MT_FUNC, "ORANGE", menu_marker_color_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_markers[] = +{ + Menuitem ( MT_MENU, "\2SELECT\0MARKER", menu_select_marker ), + Menuitem ( MT_MENU, "\2SELECT\0COLOR", menu_marker_color ), + Menuitem ( MT_FUNC, "\2ENABLE/\0DISABLE", menu_markers_cb ), + Menuitem ( MT_FUNC, "\2ENABLE\0ALL", menu_markers_cb ), + Menuitem ( MT_FUNC, "\2DISABLE\0ALL", menu_markers_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_display[] = // Display menu +{ + Menuitem ( MT_MENU, "TRACES", menu_traces ), // Turn display traces on and off + Menuitem ( MT_MENU, "\2PREAMP\0GAIN", menu_dBgain ), // Set preamp gain + Menuitem ( MT_FUNC, "\2REF\0LEVEL", menu_scale_cb ), // Set top line of the grid + Menuitem ( MT_MENU, "dB/DIV", menu_dBper ), // Menu to set vertical grid scale + Menuitem ( MT_MENU, "\2SPUR\0REDUCTION", menu_spur ), // Enable or disable spur reduction + Menuitem ( MT_FUNC, "\2DEFAULT\0SETTINGS",menu_autosettings_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_sweep2[] = +{ + Menuitem ( MT_MENU, "RBW", menu_rbw ), // Set the resolution bandwidth + Menuitem ( MT_FUNC, "ATTEN", menu_atten_cb ), // Set the attenuation + Menuitem ( MT_MENU, "MARKERS", menu_markers ), // Marker sub menu + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_sweep[] = // This is the main "SWEEP" menu +{ + Menuitem ( MT_FUNC, "\2SWEEP\0START", menu_sweep_cb ), + Menuitem ( MT_FUNC, "\2SWEEP\0STOP", menu_sweep_cb ), + Menuitem ( MT_FUNC, "\2SWEEP\0CENTER", menu_sweep_cb ), + Menuitem ( MT_FUNC, "\2SWEEP\0SPAN", menu_sweep_cb ), + Menuitem ( MT_FUNC, "\2FOCUS\0FREQ", menu_sweep_cb ), + Menuitem ( MT_MENU, "MORE->", menu_sweep2 ), // Additional options above + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_output[] = // The "OUTPUT" menu +{ + Menuitem ( MT_MENU, "REFERENCE", menu_refer ), // Select GPIO2 reference frequency + Menuitem ( MT_FUNC, "GENERATOR", menu_generate_cb ), // Turn generator mode on or off +#ifdef SI_TG_IF_CS + Menuitem ( MT_MENU, "TRACKING", menu_tracking ), // Tracking generator control +#endif + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_touchscreen[] = +{ + Menuitem ( MT_FUNC, "\2TOUCH\0CAL", menu_touch_cb ), // Calibrate touch screen + Menuitem ( MT_FUNC, "\2TOUCH\0TEST", menu_touch_cb ), // Test touch screen calibration + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +/* + * WA2FZW - Still to do - Add items to do the frequency calibrations. + */ + +static Menuitem menu_calibrate[] = +{ + Menuitem ( MT_FUNC, "TX FREQ", menu_tune_cb ), + Menuitem ( MT_FUNC, "RX FREQ", menu_tune_cb ), + Menuitem ( MT_FUNC, "\2ACTUAL\0POWER", menu_actualpower_cb ), // Calibrate power setting + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + +static Menuitem menu_config[] = // The "CONFIG" menu +{ + Menuitem ( MT_MENU, "CALIBRATE", menu_calibrate ), // Calibration menu + Menuitem ( MT_MENU, "\2TOUCH\0SCREEN", menu_touchscreen ), // Calibrate & test the touch screen + Menuitem ( MT_FUNC, "\2IF\0FREQ", menu_IF_freq_cb ), // Set the IF frequency + Menuitem ( MT_FUNC, "\2SAVE\0CONFIG", menu_save_config_cb ), // Save "config" structure + Menuitem ( MT_FUNC, "VERSION", menu_version_cb ), // Display "About" information + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + + +static Menuitem menu_sig_gen[] = // The "CONFIG" menu +{ + Menuitem ( MT_MENU, "MODE", menu_mode ), + Menuitem ( MT_FUNC, "\2SET\0FREQ", menu_sig_freq_cb ), // Set the output frequency + Menuitem ( MT_FUNC, "\2SET\0dBm", menu_sig_level_cb ), // Set the output level + Menuitem ( MT_FUNC, "\2CAL\0dBm", menu_sig_levCal_cb ), // Calibrate the sig gen output level + Menuitem ( MT_END ) // End marker +}; + + + +static Menuitem menu_IFsweep_top[] = // This is the main "IF_SWEEP" menu +{ + Menuitem ( MT_MENU, "MODE", menu_mode ), + Menuitem ( MT_FUNC, "\2SWEEP\0START", menu_IF_sweep_cb ), + Menuitem ( MT_FUNC, "\2SWEEP\0STOP", menu_IF_sweep_cb ), + Menuitem ( MT_FUNC, "\2SWEEP\0SIG", menu_IF_sweep_cb ), + Menuitem ( MT_MENU, "REFERENCE", menu_refer ), // Select GPIO2 reference frequency + Menuitem ( MT_END ) // End marker +}; + + +static Menuitem menu_Bandscope_top[] = // This is the main "IF_SWEEP" menu +{ + Menuitem ( MT_MENU, "MODE", menu_mode ), + Menuitem ( MT_FUNC, "\2SWEEP\0START", menu_Bandscope_cb ), + Menuitem ( MT_FUNC, "\2SWEEP\0SPAN", menu_Bandscope_cb ), + Menuitem ( MT_END ) // End marker +}; + + + +/* + * And last but not least the main menu! + */ + +static Menuitem menu_top[] = +{ + Menuitem ( MT_MENU, "MODE", menu_mode ), + Menuitem ( MT_MENU, "SWEEP", menu_sweep ), + Menuitem ( MT_MENU, "DISPLAY", menu_display ), + Menuitem ( MT_MENU, "STORAGE", menu_storage ), + Menuitem ( MT_MENU, "OUTPUT", menu_output ), + Menuitem ( MT_MENU, "\2SAVE/\0RECALL", menu_save_recall ), + Menuitem ( MT_MENU, "CONFIG", menu_config ), + Menuitem ( MT_END ) // End marker +}; + + +static uint8_t menu_current_level = 0; // Current level (MAIN) + +static Menuitem *menu_stack[MENU_STACK_DEPTH] = // Initialize the stack +{ menu_top, // Main menu is on top + NULL, // These get set as we + NULL, // go along + NULL +}; + + +/* + * Some definitions and more variables associated with the menu handling: + */ + + #define OFFSETX 15 // "X" offset (from what?) + #define OFFSETY 0 // "Y" offset + + +/* + * Menu handling functions: + */ + +static int touch_check ( void ); + +static void touch_wait_release ( void ) // Wait for touch screen release +{ + int tstatus; + + do + { + tstatus = touch_check (); // Check the touch screen status + } while ( tstatus != EVT_TOUCH_RELEASED ); // Loop until released +} + + +static int touch_status ( void ) // Get the touch screen status +{ + return ( tft.getTouchRawZ() > TS_MINIMUM_Z ); // "RawZ" is a measure of touch pressure +} + + +#define RAWERR 20 // Deadband error allowed in successive position samples + + +/* + * "validTouch" - Added by M0WID in Version 05: + * + * Adds a lot of checking to make sure that the user actually intended to touch + * something as opposed to a shaky-hand touch! + * + * WA2FZW - Why use the un-calibrated touch values here and the convert them + * to calibrated values in "getTouch"? + */ + +uint8_t validTouch ( uint16_t *x, uint16_t *y, uint16_t threshold ) +{ + uint16_t x_tmp, y_tmp; // "X" and "Y coordinates + static uint16_t x_tmp2, y_tmp2; // Second "X" and "Y coordinates + + +/* + * Wait until pressure stops increasing to debounce pressure, but only? + */ + + uint16_t z1 = 1; + uint16_t z2 = 0; + + while ( z1 > z2 ) + { + z2 = z1; // Old pressure = last pressure + z1 = tft.getTouchRawZ (); // Read new pressure + + if ( z1 <= threshold ) // return immediately + return false; + } + +// Serial.print ( "Z = " ); Serial.println ( z1 ); // Debugging + + if ( z1 <= threshold ) // If pressure less than the specified threshold + return false; // It's not a valid touch + +// x_tmp2 = x_tmp; // Save values from last scan +// y_tmp2 = y_tmp; + + tft.getTouchRaw ( &x_tmp, &y_tmp ); // Get touch coordinates + +// Serial.print ( "Sample 1 x,y = " ); // Debugging +// Serial.print ( x_tmp ); +// Serial.print ( "," ); +// Serial.print ( y_tmp ); +// Serial.print ( "Z = " ); +// Serial.println ( z1 ); + + delayMicroseconds ( 1000 ); // Small delay to the next sample + + if ( tft.getTouchRawZ() <= threshold ) // Threshold not met? + return false; // Then not a valid touch + + delayMicroseconds ( 1000 ); // Small delay to the next sample + + tft.getTouchRaw ( &x_tmp2, &y_tmp2 ); // Get 2nd set of coordinates + +// Serial.print ( "Sample 2 x,y = " ); // More debugging +// Serial.print ( x_tmp2 ); +// Serial.print ( ", " ); +// Serial.println ( y_tmp2 ); +// Serial.print ( "Sample difference = " ); +// Serial.print ( abs ( x_tmp - x_tmp2 )); +// Serial.print ( "," ); +// Serial.println ( abs (y_tmp - y_tmp2 )); + + if ( abs ( x_tmp - x_tmp2 ) > RAWERR ) // Error limit exceeded? + return false; // Then invalid touch + + if ( abs (y_tmp - y_tmp2 ) > RAWERR ) // Same check for "Y" values + return false; + + *x = x_tmp; // Set good + *y = y_tmp; // Coordinates + + return true; // And indicate valid touch +} + + +/* + * "getTouch" - Added by M0WID in Version 05: + * + * Replaces the TFT_eSPI library function. Returns false if not a valid touch. + * If it is a valid touch, the calibrated x and y values returned in the + * arguments. + */ + +#define RELEASE_COUNT 10 +#define PRESS_COUNT 3 + +uint8_t getTouch ( uint16_t *x, uint16_t *y ) +{ + uint16_t x_tmp, y_tmp; // Temporary touch coordinates + static unsigned long pressTime; + static int lastState; + static uint16_t threshold = TS_MINIMUM_Z; + uint8_t n; + + if ( lastState ) + { + threshold = 50; // Change threshold limit if valid to provide hysteresis + n = RELEASE_COUNT; // Need this no of not pressed read results to return not pressed + } + + else + { + threshold = TS_MINIMUM_Z; // Higher limit to indicate pressed + n = PRESS_COUNT; // Lower number of counts needed + } + + int valid = 0; // Non zero if valid touch seen + + while ( n-- ) + { + if ( validTouch ( &x_tmp, &y_tmp, threshold )) + valid++; // WA2FZW - Removed extra semicolon + else + valid--; // M0WID - add check that it is really released + } + +// Serial.printf ( "last %i valid %i\n", lastState, valid ); + + if (( lastState && (valid <= -RELEASE_COUNT+1)) || (!lastState && (valid < PRESS_COUNT-1))) // Not a valid touch + { +// pressTime = 0; // Time = '0' + lastState = false; + return false; // Indicate invalid touch + } + + +/* + * If we get this far touch is valid + */ + +// pressTime = millis() + 50; // Debounce? + + tft.convertRawXY ( &x_tmp, &y_tmp ); // Calibrate the readings + + if ( x_tmp >= tft.width () || y_tmp >= tft.height ()) // Off the chart? + { + lastState = false; + return false; // Then bad touch + } + + *x = x_tmp; // Set return values + *y = y_tmp; + lastState = true; + return true; // Indicate touch is valid +} + + +static int touch_check ( void ) // Check for TS touched +{ + uint16_t x = 0; // Used to store the "X" and + uint16_t y = 0; // "Y" coordinates + + int stat = getTouch ( &x, &y ); // Read the touch screen + +// Serial.printf ( "TouchCheck stat=%i, x=%i, y=%i \n", stat, x, y ); // Debugging + + if ( stat ) // Valid touch if non-zero + { + last_touch_x = x; // Save coordinates for ??? + last_touch_y = y; + } + + if ( stat != last_touch_status ) // Did the status change? + { + last_touch_status = stat; // Yes make new status old + + if ( stat ) // If non-zero screen was touched + return EVT_TOUCH_PRESSED; // Indicate that fact + else + return EVT_TOUCH_RELEASED; + } + + else // Status didn't change + { + if ( stat ) // If non-zero + return EVT_TOUCH_DOWN; // Must still be touched + else // If "stat" is zero + return EVT_TOUCH_NONE; // Nothing happening! + } +} + + +/* + * "DrawString" - Paint a character in a specified size and color: + * + * "t" The character + * "x & y" Coordinates + * "tc" Text color + * "size" Font size + * + * Modified in Version 2.2 by WA2FZW: + * + * Changed the name from "DrawStringSize" to "DrawString" and eliminated the + * "DrawString_5x7" function and replaced all calls to that with calls to this one. + * + * Eliminated the "bc" (background color) argument, so everything displays with + * a transparent background. + */ + +void DrawString ( const char *t, int x, int y, int tc, int size ) +{ + + //Serial.println ( t ); // Debugging + + tft.setCursor ( x, y ); // Set location + tft.setTextColor ( tc ); // Set text color + tft.setTextSize ( size ); // and font size + tft.print ( t ); // Paint the character +} + + +/* + * "touch_cal_exec" - Runs the calibration procedure built into the "TFT_eSPI" + * library. + */ + +void touch_cal_exec ( void ) +{ + TouchCalibrate (); // Function is in "tinySA.cpp" +} + + +/* + * "touch_position" returns the calibrated x/y value corrected for screen rotation setting + * when using the "ESP_eSPI" library. + */ + +void touch_position ( int *x, int *y ) +{ + *x = last_touch_x; + *y = last_touch_y; + +// Serial.print ( "TP = " ); +// Serial.print ( *x ); +// Serial.print ( ", " ); +// Serial.println ( *y ); +} + + +/* + * "touch_draw_test" lets you scribble on the display + */ + +void touch_draw_test ( void ) +{ + int status; // Touch screen status + int x0, y0; // Point '0' + int x1, y1; // Point '1' + + tft.fillRect ( 0, 0, 320, 240, 0 ); // Fade to black + + DrawString ( "TOUCH TEST: DRAG PANEL", OFFSETX, 233, WHITE, 1 ); + + do + { + status = touch_check(); + } while ( status != EVT_TOUCH_PRESSED ); + + touch_position ( &x0, &y0 ); + + do + { + status = touch_check (); + touch_position ( &x1, &y1 ); + + tft.drawLine ( x0, y0, x1, y1, WHITE ); + + x0 = x1; + y0 = y1; + } while ( status != EVT_TOUCH_RELEASED ); +} + + +void ShowVersion ( void ) +{ +char ipBuff[60]; // To format IP address + + ShowSplash (); // Like "Help - About" + tft.setTextDatum ( TC_DATUM ); // Top center text position datum + + sprintf ( ipBuff, "IP address %s", FormatIPAddress ( ipAddress )); + tft.drawString ( ipBuff, 160, 140 ); + tft.setTextDatum ( TL_DATUM ); // Back to default top left + + while ( true ) + { + if ( touch_check () == EVT_TOUCH_PRESSED ) // Wait for screen to be touched + break; // And bail out + } +} + + +/* + * "ShowSplash" displays a screen like a "Help - About" screen + */ + +void ShowSplash ( void ) +{ + ClearDisplay (); // Fade to black! + + tft.setTextDatum ( TC_DATUM ); // Top center text position datum + + tft.setTextColor ( MAGENTA ); + + tft.setFreeFont ( &FreeSerifBoldItalic18pt7b ); // Select Free Serif 9 point font + tft.drawString ( "TinySA for ESP32", 160, 20 ); + tft.setTextColor ( WHITE ); + tft.setFreeFont ( &FreeSansBold9pt7b ); // Select Free Serif 9 point font + tft.drawString ( "By WA2FZW, M0WID,", 160, 60 ); + tft.drawString ( "VK3PE and G3ZQC", 160, 80 ); + tft.drawString ( "Version 3.0", 160, 100 ); + tft.drawString ( "Original by Erik (PD0EK)", 160, 120 ); + + tft.setTextDatum ( TL_DATUM ); // Back to default top left + + tft.setCursor ( 0, 120 ); // Position cursor for any more messages + tft.setFreeFont ( NULL ); // Select default font +} + + +void enter_dfu(void) {} // Do nothing function + + +void request_to_redraw_grid() // Clear the display and update +{ + ClearDisplay (); + changedSetting = true; + menuExit(); // Handle the return from the menu +// RedrawHisto (); +} + + +void draw_frequencies() {} // Do nothing + + + +/* + * "menu_mode_cb" - handles the process of setting the operating mode + */ + +void menu_mode_cb ( int item ) +{ + switch ( item ) + { + case 0: // Set Sweep low range + setMode(SA_LOW_RANGE); + ui_mode_normal (); // No menu displayed + break; + + case 1: // Set Signal Generator + setMode(SIG_GEN_LOW_RANGE); + ui_mode_normal (); // No menu displayed + break; + + case 2: // Set IF Sweep mode + setMode(IF_SWEEP); + ui_mode_normal (); // No menu displayed + break; + + case 3: // Set IF Sweep mode + setMode(BANDSCOPE); + ui_mode_normal (); // No menu displayed + break; + } + +} + + + +/* + * "menu_save_config_cb" - Saves the "config" structure (for use after calibration) + */ + +void menu_save_config_cb ( int item ) +{ + WriteConfig (); // Simple enough! + menu_move_back(); + ui_mode_normal(); +} + + +/* + * "menu_tune_cb" - handles the process of calibrating frequencies of the Si4432 + * modules. See the documentation for an explanation of how to perform the calibration. + */ + +void menu_tune_cb ( int item ) +{ + switch ( item ) + { + case 0: // Calibrate TX + VFO = TX_4432; // Select transmitter module + break; + + case 1: // Calibrate RX + VFO = RX_4432; // Select receiver module + break; + } + + int km = KM_TUNE; + ui_mode_keypad ( km ); + ui_process_keypad (); + +} + + +void menu_marker_color_cb ( int item ) +{ + char colors[6] = { 'W', 'R', 'B', 'G', 'Y', 'O' }; + + UpdateMarker ( selectedMarker, colors[item] ); + draw_menu (); + menu_move_back(); +} + +void menu_marker_select_cb ( int item ) +{ + selectedMarker = item; + draw_menu (); + menu_move_back(); +} + + +/* + * "menu_markers_cb" - Turn markers on or off + */ + +void menu_markers_cb ( int item ) +{ + switch ( item ) + { + case 0: // Now handled by "menu_marker_select_cb" + break; // So do othing + + case 2: // Toggle the currently selected marker + marker[selectedMarker].Toggle(); + draw_menu (); + break; + + case 3: // Turn them all on + for ( int i = 0; i < MARKER_COUNT; i++ ) + marker[i].Enable(); + ui_mode_normal(); + break; + + case 4: // Turn them all off + for ( int i = 0; i < MARKER_COUNT; i++ ) + marker[i].Disable(); + ui_mode_normal(); + break; + } + + for ( int i = 0; i < MARKER_COUNT; i++ ) + setting.MkrStatus[i] = marker[i].Status (); + + WriteSettings (); +} + + +/* + * "menu_IF_freq_cb" - Handles setting the IF Frequency + */ + +void menu_IF_freq_cb ( int item ) +{ + int km = KM_IFFREQ; + ui_mode_keypad ( km ); + ui_process_keypad (); +} + + +/* + * "menu_sig_freq_cb" - Handles setting the signal generator Frequency + */ + +void menu_sig_freq_cb ( int item ) +{ + int km = KM_SGFREQ; + tft.fillScreen ( bg ); + ui_mode_keypad ( km ); + ui_process_keypad (); +} + + +/* + * "menu_sig_level_cb" - Handles setting the signal generator output level + */ + +void menu_sig_level_cb ( int item ) +{ + int km = KM_SGLEVEL; + tft.fillScreen ( bg ); + ui_mode_keypad ( km ); + ui_process_keypad (); +} + + +/* + * "menu_sig_levCal_cb" - Handles setting the signal generator max level (no attenuation) + */ + +void menu_sig_levCal_cb ( int item ) +{ + int km = KM_SGLEVCAL; + tft.fillScreen ( bg ); + ui_mode_keypad ( km ); + ui_process_keypad (); +} + + +/* + * "menu_tg_offset_cb" - Handles setting the tracking generator offset from SA IF + */ + +void menu_tg_offset_cb ( int item ) +{ + int km = KM_TGOFFSET; + tft.fillScreen ( bg ); + ui_mode_keypad ( km ); + ui_process_keypad (); +} + + + +/* + * "menu_tgIF_drive_cb" - Handles setting the tracking generator IF drive + */ + +void menu_tgIF_drive_cb ( int item ) +{ + int km = KM_TGIF_DRIVE; + tft.fillScreen ( bg ); + ui_mode_keypad ( km ); + ui_process_keypad (); +} + + + +/* + * "menu_tgLo_drive_cb" - Handles setting the tracking generator LO drive + */ + +void menu_tgLO_drive_cb ( int item ) +{ + int km = KM_TGLO_DRIVE; + tft.fillScreen ( bg ); + ui_mode_keypad ( km ); + ui_process_keypad (); +} + + + +/* + * "menu_generate_cb" - M0WID Version 05; replaces the function "menu_output_cb" + * from previous versions. + */ + +static void menu_generate_cb ( int item ) +{ + SetGenerate ( 1 ); // Item 0 is off, 1 is on +// selection = item; +// draw_menu (); +// menu_move_back(); + ui_mode_normal (); // No menu displayed +} + + +/* + * menu_tracking_cb handles settings for the tracking generator + */ +static void menu_tracking_cb (int item ) +{ + switch ( item ) + { + case 0: // Turn Off + SetTracking (0); + ui_mode_normal (); // Back to sweep + break; + + case 1: // Turn On + SetTracking (1); + ui_mode_normal (); // Back to sweep + break; + + } + +} + +/* + * "menu_autosettings_cb" seems to set all the defaults then clears the menu + * from the display. + * + * Modified in Version 2.5 by WA2FZW: + * + * Added definitions in "My_SA.h" to allow the user to customize the "AUTO" settings. + */ + +static void menu_autosettings_cb ( int item ) +{ + SetSweepStart ( AUTO_SWEEP_START ); // Scan limits + SetSweepStop ( AUTO_SWEEP_STOP ); + SetPowerGrid ( AUTO_PWR_GRID ); + SetPreampGain ( AUTO_LNA ); + SetRefLevel ( AUTO_REF_LEVEL ); + SetRefOutput ( AUTO_REF_OUTPUT ); + SetAttenuation ( AUTO_ATTEN ); +// SetPowerLevel ( 100 ); // Removed - Screws up calibration + SetRBW ( AUTO_RBW ); // Auto mode + SetRX ( 0 ); + setting.ShowGain = 1; // Gain trace on + setting.ShowSweep = 1; // Main sweep trace on + + menu_move_back (); + ui_mode_normal (); // No menu displayed +} + + + +/* + * "menu_touch_cb" is the dispatcher for part of the "TOUCH SCREEN" menu + */ + +static void menu_touch_cb ( int item ) +{ + switch ( item ) + { + case 0: // All these need symbols! + touch_cal_exec (); + request_to_redraw_grid (); + draw_menu (); + break; + + case 1: + touch_draw_test (); + request_to_redraw_grid (); + draw_menu (); + break; + } +} + +/* + * "menu_version_cb" handles displaying the "About" screen + */ + +static void menu_version_cb ( int item ) +{ + ShowVersion (); + request_to_redraw_grid (); + draw_menu (); +} + + +/* + * "menu_save_cb" saves the current "setting" structure in a named location in the + * flash memory. The "preferences Save" function adds the "item" number to the string + * "Save" to create the saved name. + */ + +static void menu_save_cb ( int item ) +{ + if ( item < 0 || item > 4 ) // Legal "item" number? + return; // Nope + + Save ( item ); + menu_move_back(); + ui_mode_normal(); +} + + +/* + * "menu_recall_cb" - Works like "menu_save_cb" in reverse + */ + +static void menu_recall_cb ( int item ) +{ + if ( item < 0 || item > 4 ) // Test for illegal "item" + return; + + Recall ( item ); + menu_move_back(); + ui_mode_normal(); +} + + +/* + * "menu_refer_cb" - Set reference level from 1st page of the "REFERENCE" menu + */ + +static void menu_refer_cb ( int item ) +{ +// Serial.println ( item ); // Debugging + + SetRefOutput ( menu_refer_value[item] ); + menu_move_back(); + ui_mode_normal(); +} + + +/* + * "menu_refer_cb2" - Set reference output frequency from 2nd page + * of the "REFERENCE" menu + */ + +static void menu_refer_cb2 ( int item ) +{ +// Serial.println ( item ); // Debugging + + SetRefOutput ( menu_refer_value[item+5] ); // Items 0 - 4 are on page 1 + menu_move_back (); + ui_mode_normal (); +} + + +/* + * "menu_spur_cb" - Handles the "Spur Reduction" menu item. + */ + +static void menu_spur_cb ( int item ) +{ + SetSpur ( item ); + menu_move_back (); + ui_mode_normal (); +} + + +/* + * "menu_storage_cb" - Handles the "STORAGE" menu item. + */ + +static void menu_storage_cb ( int item ) +{ + switch ( item ) + { + case 0: // "STORE" + SetStorage (); + break; + + case 1: + SetClearStorage (); // "CLEAR" + break; + + case 2: // "SUBTRACT" - What does this mean? + SetSubtractStorage (); + break; + } + + menu_move_back (); + ui_mode_normal (); +} + + +/* + * "menu_average_cb" - Handles the "AVERAGE" menu item. + */ + +static void menu_average_cb ( int item ) +{ + SetAverage ( item ); + menu_move_back (); + ui_mode_normal (); +} + + +/* + * "menu_rbw_cb" - Handles the "RBW" menu item. + * + * WA2FZW - Moved the "rbwsel" choices into the function; they were previously + * defined in global space. + */ + +static void menu_rbw_cb ( int item ) +{ +const int rbwsel[] = { 0, 31, 106, 322, 1121, 3355 }; // Resolution bandwidth choices (in KHz * 10) + + SetRBW ( rbwsel[item] ); + menu_move_back (); + ui_mode_normal (); +} + + +/* + * "menu_dBper_cb" - Handles the "SCALE/DIV" menu item + */ + +static void menu_dBper_cb ( int item ) +{ +int menu_dBper_value[] = { 1, 2, 5, 10, 20 }; // Scale setting values (dB/division) + + SetPowerGrid ( menu_dBper_value[item] ); + menu_move_back (); + ui_mode_normal (); +} + + +/* + * "menu_dBgain_cb" - Handles the "PREAMP GAIN" menu item. + */ + +static void menu_dBgain_cb ( int item ) +{ + if ( item == 0 ) // Turn the AGC on + { + SetPreampGain ( 0x60 ); // ACG on value + menu_move_back (); + ui_mode_normal (); + } + + else // Get value from the keypad + { + int km = KM_PREAMP; + ui_mode_keypad ( km ); + ui_process_keypad (); + } + +} + + +/* + * "choose_active_trace" - Holdover from NanoVNA but left in as it might be used + * in the future in the TinySA. + */ + +static void choose_active_trace ( void ) +{ + int i; + + if ( trace[uistat.current_trace].enabled ) // If true + return; // Do nothing + + for ( i = 0; i < TRACE_COUNT ; i++ ) // "TRACE_COUNT" is set to '1' somewhere + if ( trace[i].enabled ) // Find first "enabled: trace + { + uistat.current_trace = i; // Save the index + return; + } +} + + +/* + * Repurposed in Version 2.6 by WA2FZW: + * + * Now toggles the main sweep trace or the gain trace on or off. + * + * + * Like so many other of these functions "item" is the position of the item in the + * menu; this has to be fixed! + * + * Here, item '0' is the main sweep trace and item '1' is the gain trace. + */ + +static void menu_trace_cb ( int item ) +{ + switch ( item ) + { + case 0: // Toggle the main sweep + setting.ShowSweep = !setting.ShowSweep; + + break; + + case 1: // Toggle the gain trace + setting.ShowGain = !setting.ShowGain; + + break; + } + + WriteSettings (); + menu_move_back (); + ui_mode_normal (); +} + + +static void menu_atten_cb ( int item ) +{ + int km = KM_ATTENUATION; + ui_mode_keypad ( km ); + ui_process_keypad (); +} + + +/* + * "menu_scale_cb" - Handles the "REF LEVEL" menu item. + */ + +static void menu_scale_cb ( int item ) +{ + int km = KM_REFPOS; + ui_mode_keypad ( km ); + ui_process_keypad (); +} + + +/* + * "menu_actualpower_cb" - Handles the "ACTUAL POWER" menu item. + */ + +static void menu_actualpower_cb ( int item ) +{ + int km = KM_ACTUALPOWER; + ui_mode_keypad ( km ); + ui_process_keypad (); +} + + +/* + * "menu_sweep_cb" - Handles the menu items under the "SCAN" menu. + * + * Modified in Version 2.5 by WA2FZW: + * + * Eliminated "case 5" which was the handler for "Pause Sweep". That menu + * choice really didn't do anything, so it was eliminated altogether. I left + * the switch intact should we wish to add something to the "Scan" menu list. + * + * + * Modified in Version 2.9 by WA2FZW: + * + * Totally eliminated the "switch" as all the menu items that set frequencies + * are handled the same. + */ + +static void menu_sweep_cb ( int item ) +{ + ui_mode_keypad ( item ); + ui_process_keypad (); +} + + + +static void menu_IF_sweep_cb ( int item ) +{ + ui_mode_keypad ( item + KM_IFSTART - 1 ); // item = 1 -> KM_IFSTART, 2 -> KM_IFSTOP, 3-> KM_IFSIG + ui_process_keypad (); +} + +static void menu_Bandscope_cb ( int item ) +{ + ui_mode_keypad ( item + KM_BANDSCOPESTART - 1 ); // item = 1 -> KM_BANDSCOPESTART, 2 -> KM_BANDSCOPESPAN + ui_process_keypad (); +} + +/* + * "ensure_selection" - Validates that a menu selection is valid + */ + +static void ensure_selection ( void ) +{ + Menuitem *menu = menu_stack[menu_current_level]; + int i; + + for ( i = 0; menu[i].Type() != MT_END; i++ ) {} // Drops out when the "END" is found + + if ( selection >= i ) + selection = i - 1; +} + + +/* + * "menu_move_back" - Go back to the previous menu level + */ + +static void menu_move_back ( void ) +{ + if ( menu_current_level == 0 ) + return; + +// Serial.print ( "Poplevel=" ); +// Serial.println ( menu_current_level ); + + menu_current_level--; + ensure_selection (); // Make sure valid + erase_menu_buttons (); // Erase old buttons + draw_menu (); // Paint new ones +} + + +/* + * "menu_push_submenu" - Paint a new sub-menu + */ + +static void menu_push_submenu ( Menuitem *submenu ) +{ + +// Serial.print( "Pushlevel=" ); +// Serial.println ( menu_current_level ); + + if ( menu_current_level < MENU_STACK_DEPTH - 1 ) + menu_current_level++; + + menu_stack[menu_current_level] = submenu; + ensure_selection (); + erase_menu_buttons (); + draw_menu (); +} + + +/* + * "menu_invoke" - + */ + +static void menu_invoke ( int item ) +{ + Menuitem *menu = menu_stack[menu_current_level]; + menu = &menu[item]; + +// Serial.print ( "Invoke=" ); +// Serial.print ( item ); +// Serial.print ( ", type=" ); +// Serial.print ( menu->type ); +// Serial.print ( ", label= " ); +// Serial.println ( menu->label ); + + switch ( menu->Type() ) + { + case MT_END: + ui_mode_normal (); // Clear menu display + break; + + case MT_BACK: // Go up one level + menu_move_back (); + break; + + case MT_FUNC: // Call function to process selection + { + menu->Call ( item ); + break; + } + + case MT_MENU: // Display a sub-menu + menu_push_submenu ( menu->GetSubmenu () ); + break; + } +} + + +/* + * All the following stuff has to do with the keypad. The first two definitions + * are the 'x' and 'y' coordinates of the entire keypad. The "KP_X" and "KP_Y" + * macros set the 'x' and 'y' pixel coordinates for the key based on its row + * and column in the keypad; pretty clever! + */ + +#define KP_X(x) ( 48 * ( x ) + 2 + ( 320 - 64 - 192 )) +#define KP_Y(y) ( 48 * ( y ) + 2 ) + + + +typedef struct +{ + uint16_t x, y; // X and Y coordinates of a key + int8_t c; // Index to the "kpf" array of characters? +} keypads_t; + +static const keypads_t *keypads; // Pointer to array of keypads +static uint8_t keypads_last_index; // Index into the array + + +/* + * There are different keypads for different things. + * + * The numbers in the "KP_X(n)" and "KP_Y(n) macro calls are the X and Y positions + * of the keys the columns (X) are numbered from left to right as 0 to 3 and the + * rows (Y) are numbered from top to bottom as 0 to 3. + * + * WA2FZW - Some space can be saved by creating one generic keypad with the digits + * and decimal point and then individual supplemental ones for the special keys on + * eash of the following. + * + * The first one is for entering frequencies. + */ + +static const keypads_t keypads_freq[] = // Keypad array for frequencies +{ + { KP_X(1), KP_Y(3), KN_PERIOD }, // Decimal point + { KP_X(0), KP_Y(3), 0 }, // '0' digit + { KP_X(0), KP_Y(2), 1 }, // '1' digit + { KP_X(1), KP_Y(2), 2 }, + { KP_X(2), KP_Y(2), 3 }, + { KP_X(0), KP_Y(1), 4 }, + { KP_X(1), KP_Y(1), 5 }, + { KP_X(2), KP_Y(1), 6 }, + { KP_X(0), KP_Y(0), 7 }, + { KP_X(1), KP_Y(0), 8 }, + { KP_X(2), KP_Y(0), 9 }, + { KP_X(3), KP_Y(1), KN_M }, // Megahertz + { KP_X(3), KP_Y(2), KN_K }, // Kilohertz + { KP_X(3), KP_Y(3), KN_X }, // Enter + { KP_X(2), KP_Y(3), KN_BS }, // Backspace + { 0, 0, -1 } // Array end marker +}; + + +static const keypads_t keypads_signed_freq[] = // Keypad array for signed frequencies +{ + { KP_X(1), KP_Y(3), KN_PERIOD }, // Decimal point + { KP_X(0), KP_Y(3), 0 }, // '0' digit + { KP_X(0), KP_Y(2), 1 }, // '1' digit + { KP_X(1), KP_Y(2), 2 }, + { KP_X(2), KP_Y(2), 3 }, + { KP_X(0), KP_Y(1), 4 }, + { KP_X(1), KP_Y(1), 5 }, + { KP_X(2), KP_Y(1), 6 }, + { KP_X(0), KP_Y(0), 7 }, + { KP_X(1), KP_Y(0), 8 }, + { KP_X(2), KP_Y(0), 9 }, + { KP_X(3), KP_Y(0), KN_MINUS }, + { KP_X(3), KP_Y(1), KN_M }, // Megahertz + { KP_X(3), KP_Y(2), KN_K }, // Kilohertz + { KP_X(3), KP_Y(3), KN_X }, // Enter + { KP_X(2), KP_Y(3), KN_BS }, // Backspace + { 0, 0, -1 } // Array end marker +}; + +static const keypads_t keypads_integer[] = // Keypad array for integers (no decimal point) +{ + { KP_X(1), KP_Y(3), 0 }, + { KP_X(1), KP_Y(2), 1 }, + { KP_X(2), KP_Y(2), 2 }, + { KP_X(3), KP_Y(2), 3 }, + { KP_X(1), KP_Y(1), 4 }, + { KP_X(2), KP_Y(1), 5 }, + { KP_X(3), KP_Y(1), 6 }, + { KP_X(1), KP_Y(0), 7 }, + { KP_X(2), KP_Y(0), 8 }, + { KP_X(3), KP_Y(0), 9 }, + { KP_X(3), KP_Y(3), KN_X }, + { KP_X(2), KP_Y(3), KN_BS }, + { 0, 0, -1 } +}; + +static const keypads_t keypads_level[] = // Used for "ACTUAL POWER" input +{ + { KP_X(1), KP_Y(3), KN_PERIOD }, + { KP_X(0), KP_Y(3), 0 }, + { KP_X(0), KP_Y(2), 1 }, + { KP_X(1), KP_Y(2), 2 }, + { KP_X(2), KP_Y(2), 3 }, + { KP_X(0), KP_Y(1), 4 }, + { KP_X(1), KP_Y(1), 5 }, + { KP_X(2), KP_Y(1), 6 }, + { KP_X(0), KP_Y(0), 7 }, + { KP_X(1), KP_Y(0), 8 }, + { KP_X(2), KP_Y(0), 9 }, + { KP_X(3), KP_Y(2), KN_MINUS }, + { KP_X(3), KP_Y(3), KN_X }, + { KP_X(2), KP_Y(3), KN_BS }, + { 0, 0, -1 } +}; + + +/* + * This array is indexed by the enum at the top of the file that defines the values for + * "KM_START", KM_STOP, etc. + */ + +static const keypads_t * const keypads_mode_tbl[] = +{ + keypads_freq, // KM_START..........Sweep start frequency + keypads_freq, // KM_STOP...........Sweep stop frequency + keypads_freq, // KM_CENTER.........Sweep center frequency + keypads_freq, // KM_SPAN...........Sweep frequency span + keypads_freq, // KM_FOCUS..........Focus Frequency + keypads_level, // KM_REFPOS.........Grid reference level (top line) + keypads_integer, // KM_ATTENUATION....Attenuation + keypads_level, // KM_ACTUALPOWER....Power level calibration + keypads_freq, // KM_IFFREQ.........IF frequency + keypads_integer, // KM_PREAMP.........Preamp gain + keypads_integer, // KM_TUNE...........Transceiver crystal load + keypads_freq, // KM_SGFREQ.........Sig Gen frequency + keypads_level, // KM_SGLEVEL........Sig Gen Power level + keypads_level, // KM_SGLEVCAL.......Power level calibration + keypads_freq, // KM_IFSTART........IF Sweep start frequency + keypads_freq, // KM_IFSTOP.........IF Sweep stop frequency + keypads_freq, // KM_IFSIG..........IF Sweep signal frequency + keypads_signed_freq, // KM_TGOFFSET.......Offset Frequency of TG IF compared to SA IF + keypads_level, // KM_TGLO_DRIVE.....Tracking generator LO drive + keypads_level, // KM_TGIF_DRIVE.....Tracking generator IF drive + keypads_freq, // KM_BANDSCOPESTART.IF Sweep start frequency + keypads_freq // KM_BANDSCOPESPAN..IF Sweep stop frequency +}; + + +/* + * "draw_keypad" - Seems pretty self-explanatory! + * + * Modified in Version 2.7 by WA2FZW: + * + * Instead of simply painting reqular characters from the (no longer existing) + * "kpf" array, the characters used in the keypad keys and in the number box are + * now painted using bitmaps in the "KP_Font" array. + * + * The "KeyNumber" function replaces the "DrawFont" function to paint the bitmaps. + */ + +static void draw_keypad ( void ) +{ + int i = 0; + + while ( keypads[i].x ) // Non zero "X" location + { + uint16_t bg = config.menu_normal_color; // Color when key is not selected + + if ( i == selection ) + bg = config.menu_active_color; // Color when a key is selected + + tft.fillRect ( keypads[i].x, keypads[i].y, 44, 44, bg ); // Set background color + + KeyNumber ( keypads[i].c, keypads[i].x+12, keypads[i].y+10 ); // Paint the character + + i++; // Next + } +} + + +/* +/* + * "KeyNumber" uses the "KP_Font" array to draw a number in a keypad key. + */ + +static void KeyNumber ( int8_t key, int x, int y ) +{ + +uint16_t bits; // Holds the bitmap for one line + +uint16_t* font = &KP_Font[key * 22]; // Calculate pointer to desired bitmap + + for ( int line = 0; line < 22; line++ ) // Do line by line; top to bottom + { + bits = font[line]; // Get the bit pattern for a line + + for ( int column = 0; column < 16; column++ ) // horizontally left to right + { + if ( bits & 0x8000 ) // Is the next pixel turned on? + tft.drawPixel ( column+x, line+y, BLACK ); // Yes, always black + + bits <<= 1; // Shift the bitmap byte one place left + } + } // Next line +} + +/* + * "draw_numeric_area_frame" - Draws the numerical box uncer the keypad + */ + +static void draw_numeric_area_frame ( void ) +{ + tft.fillRect ( 0, 208, 256, 32, WHITE ); // White background + + DrawString ( keypad_mode_label[keypad_mode], 10, 220, BLACK, 1 ); + tft.setCursor(0,230); + tft.printf("%i",uistat.previous_value); +} + + +/* + * "draw_numeric_input" - Puts numbers in the numerical entry box + * + * Modified in Version 2.7 by WA2FZW: + * + * Instead of using characters from the (no longer exists) "kpf" array to write + * in the numerical input box, the characters are now defined by bitmaps in the + * "KP_Font" array. The "KeyNumber" replaces the (no longer exists) "DrawFont" + * function. + * + * Also re-did how it decides to add space after every third digit. A leading + * minus sign no longer counts and if a decimal point is entered, we no longer + * add the extra space between digits. + */ + +static void draw_numeric_input ( const char *buf ) +{ + int i = 0; // Buffer index + int x = 58; // 'X' coordinate for first character + int nextX = 20; // Normal character spacing + bool sawDot = false; // True if decimal entered + uint8_t digits = 0; // Digit counter + + for ( i = 0; i < 10 && buf[i]; i++ ) + { + nextX = 18; // Normal character spacing + + uint16_t fg = BLACK; // Foreground is black + uint16_t bg = WHITE; // Background is white + + int c = buf[i]; // Get a character + + if ( c == '.' ) // Decimal point? + { + c = KN_PERIOD; // Index to '.' in the "KP_Font" array + sawDot = true; // Decimal seen + } + + else if ( c == '-' ) // Minus sign" + c = KN_MINUS; // Index to '_' in the "KP_Font" array + + else if ( isDigit ( c )) // Digit? + c = c - '0'; // Quick & dirty "atoi" + + else // None of the above + c = -1; + + if ( c >= 0 ) // 'c' was found + { + KeyNumber ( c, x, 208+4 ); // Paint the character +// addIx++; + + if ( c <= 9 ) // Is it a number? + { + digits++; // Increment the digit counter + if ( !sawDot ) // No decimal yet + if (( digits % 3 ) == 0 ) // Every third digit + nextX += 6; // Add extra space + } + } + + else + tft.fillRect ( x, 208+4, 20, 24, bg ); // If not found, erase it + + x += nextX; // Place for next digit + + } // End of for loop + + if ( i < 10 ) + tft.fillRect ( x, 208+4, 20 * ( 10-i ), 24, WHITE ); // Fill something with white +} + + +/* + * "draw_menu_buttons" - Paint the menu buttons + */ + +static void draw_menu_buttons ( Menuitem *menu ) +{ + int i = 0; + + //Serial.println( "----------------------------" ); // Debugging + + for ( i = 0; i < 7; i++ ) // Maximum number of menu buttons is 7 + { + if ( menu[i].Type() == MT_END ) // End of the menu + break; // So no need to look any further + + int y = 32 * i; // "Y" coordinate in pixels? + uint16_t bg = config.menu_normal_color; // Background is unselected color + uint16_t fg = BLACK; // Text is in black + + if ( ui_mode == UI_MENU && i == selection ) // focus only in MENU mode but not in KEYPAD mode + bg = config.menu_active_color; // Set background to selected color + + tft.fillRect ( 320-60, y, 60, 30, bg ); // These hard coded numbers have to go! + + if ( menu[i].isMultiline ()) // Multi-line label? + { + DrawString ( menu[i].Text1(), 320-54, y+6, fg, 1 ); // First line + DrawString ( menu[i].Text2(), 320-54, y+17, fg, 1 ); // Second line + } + + else // Single line label + DrawString ( menu[i].Text1(), 320-54, y+12, fg, 1 ); + } // End of for loop +} + + +/* + * "menu_select_touch" - Wait for a menu button to be pushed + */ + +static void menu_select_touch ( int i ) +{ + selection = i; + + draw_menu (); // Draw the menu buttons + +// Serial.println ( "Before wait release" ); // Debugging + + touch_wait_release (); // Wait for a release indication + +// Serial.println ( "After wait release" ); + + selection = -1; // Don't understand this ??? + + menu_invoke ( i ); +} + + +/* + * "menu_apply_touch" - Figure out which button was touched + */ + +static void menu_apply_touch ( void ) +{ + int touch_x, touch_y; // Touch coordinates + Menuitem *menu = menu_stack[menu_current_level]; + int i; + + touch_position ( &touch_x, &touch_y ); // Get coordinates + + for ( i = 0; i < 7; i++ ) // Up to 7 buttons + { + int y = 32 * i; // "Y" coordinate of button in pixels + + if ( y-2 < touch_y && touch_y < y+30+2 && 320-60 < touch_x ) + { + menu_select_touch ( i ); // This is the button that was selected + return; // We're done! + } + } // End of for loop + + touch_wait_release (); // Wait for button release + ui_mode_normal (); // No more menu +} + + +/* + * "draw_menu" - Paint the entire menu + */ + +static void draw_menu ( void ) +{ +// Serial.println ( "draw menu" ); // Debugging + draw_menu_buttons ( menu_stack[menu_current_level] ); // Paint the buttons +} + + +/* + * "erase_menu_buttons" - Self explanatory + */ +static void erase_menu_buttons ( void ) +{ + +// Serial.println ( "erase buttons" ); // Debugging + + tft.fillRect ( 320-60, 0, 60, 32*7, bg ); +} + + +/* + * "erase_numeric_input" - Erase the numerical input box below the keypad + */ + +static void erase_numeric_input ( void ) +{ + tft.fillRect ( 0, 240-32, 320, 32, bg ); // Poof! +} + + +/* + * "request_to_draw_cells_behind_menu" - Do nothing function + */ + +void request_to_draw_cells_behind_menu () {} + + +/* + * "leave_ui_mode" - Cleanup after using the touch screen. + * + * Modified in Version 2.1 by WA2FZW: + * + * Eliminated "changedSetting" test and "WriteSettings". Because savings the + * settings was also being handled in some of the functions that set the members + * of the "setting" structure, the structure was sometimes being saved twice. It + * was also being saved when the parameters didn't actually change. This was + * all fixed in "Cmd.cpp". + */ + +static void leave_ui_mode ( void ) +{ + if ( ui_mode == UI_MENU ) // In menu mode? + { + erase_menu_buttons (); // Erase the menu area + request_to_redraw_grid(); + } + + else if ( ui_mode == UI_NUMERIC ) // Using the keypad? + erase_numeric_input (); // Erase the number box + +// Serial.println ( "leave UI" ); // Debugging +} + + +/* + * "fetch_numeric_target" - Gets one of the scan frequency parameters. The + * "GetSweepStart()" etc functions are in the cmd.cpp file. + */ + +static void fetch_numeric_target ( void ) +{ + switch ( keypad_mode ) + { + case KM_START: + uistat.value = GetSweepStart (); + break; + + case KM_STOP: + uistat.value = GetSweepStop (); + break; + + case KM_CENTER: + uistat.value = GetSweepCenter (); + break; + + case KM_SPAN: + uistat.value = GetSweepSpan (); + break; + + case KM_FOCUS: + uistat.value = GetSweepCenter (); + break; + + case KM_REFPOS: +// uistat.value = get_trace_refpos ( uistat.current_trace ) * 1000; + break; + + case KM_ATTENUATION: + uistat.value = setting.Attenuate; + break; + + case KM_ACTUALPOWER: +// uistat.value = velocity_factor; + break; + + case KM_IFFREQ: + uistat.value = setting.IF_Freq; + break; + + case KM_SGFREQ: + uistat.value = sigGenSetting.Frequency; + break; + + case KM_SGLEVEL: + uistat.value = sigGenSetting.Power; + break; + + case KM_SGLEVCAL: + uistat.value = sigGenSetting.Calibration; + break; + + case KM_PREAMP: + uistat.value = GetPreampGain ( &AGC_On, &AGC_Reg ); + break; + + case KM_IFSTART: + uistat.value = GetIFsweepStart(); + break; + + case KM_IFSTOP: + uistat.value = GetIFsweepStop(); + break; + + case KM_IFSIG: + uistat.value = GetIFsweepSigFreq(); + break; + + case KM_TGOFFSET: + uistat.value = trackGenSetting.Offset; + break; + + case KM_TGIF_DRIVE: + uistat.value = trackGenSetting.IF_Drive; + break; + + case KM_TGLO_DRIVE: + uistat.value = trackGenSetting.LO_Drive; + break; + + case KM_BANDSCOPESTART: + uistat.value = setting.BandscopeStart; + break; + + case KM_BANDSCOPESPAN: + uistat.value = setting.BandscopeSpan; + break; + + } + + uint32_t x = uistat.value; + int n = 0; + + for (; x >= 10 && n < 9; n++ ) + x /= 10; + + uistat.digit = n; + + uistat.previous_value = uistat.value; + Serial.printf("uistat previous value %f\n", uistat.previous_value ); +} + + +/* + * "draw_numeric_area" - Paints adds the appropriate text to the numeric entry box + * and requests it to be painted. + */ + +static void draw_numeric_area ( void ) +{ + char buf[10]; + snprintf ( buf, sizeof buf, "%9d", uistat.value ); + draw_numeric_input ( buf ); +} + + +/* + * "ui_mode_menu" - Sets menu mode and paints the menu + */ + +static void ui_mode_menu ( void ) +{ + + if ( ui_mode == UI_MENU ) // If already in menu mode + return; // Nothing to do here + + ui_mode = UI_MENU; // Set menu mode + Serial.println("ui_mode_menu"); +// area_width = AREA_WIDTH_NORMAL - ( 64 - 8 ); // Narrower plotting area +// area_height = HEIGHT; + ensure_selection (); // Validate current selection + draw_menu (); // And paint the menu +} + + +/* + * "ui_mode_numeric" - Puts the UI into numeric mode + */ +static void ui_mode_numeric ( int _keypad_mode ) +{ + if ( ui_mode == UI_NUMERIC ) // Already in numeric mode? + return; // Nothing to do + + leave_ui_mode (); // Get out of previous mode? + + keypad_mode = _keypad_mode; // Set numeric mode + ui_mode = UI_NUMERIC; + +// area_width = AREA_WIDTH_NORMAL; +// area_height = 240 - 32; // HEIGHT - 32; + + draw_numeric_area_frame (); + fetch_numeric_target (); + draw_numeric_area (); +} + + +/* + * "ui_mode_keypad" - Puts us in keypad mode + */ + +static void ui_mode_keypad ( int _keypad_mode ) +{ + if ( ui_mode == UI_KEYPAD ) // Already here? + return; // Nuttin to do + + keypad_mode = _keypad_mode; // Pick the proper keypad format + keypads = keypads_mode_tbl[_keypad_mode]; + + int i; + for ( i = 0; keypads[i+1].c >= 0; i++ ) {} + + keypads_last_index = i; // Fall out of loop when found + + ui_mode = UI_KEYPAD; // Set keypad mode + +// area_width = AREA_WIDTH_NORMAL - ( 64 - 8 ); +// area_height = HEIGHT - 32; + + draw_menu (); // Paint the menu + draw_keypad (); // Then the keypad + fetch_numeric_target (); //!!!!! + draw_numeric_area_frame (); // Then the numeric entry area + draw_numeric_input (""); // Blanked out? +} + + +/* + * "ui_mode_normal" - Puts the UI into normal mode + */ + +static void ui_mode_normal ( void ) +{ + if ( ui_mode == UI_NORMAL ) // Already there? + return; // Done + +//**HERE2 +// area_width = AREA_WIDTH_NORMAL; +// area_height = HEIGHT; + + menu_current_level = 0; + + leave_ui_mode(); + ui_mode = UI_NORMAL; +} + + +/* + * "my_atof" - A homebrew ascii to double function; not sure why needed? + */ + +double my_atof ( const char *p ) +{ + int neg = FALSE; + + if ( *p == '-' ) // If first character is a minus sign + neg = TRUE; // Number is negative + + if ( *p == '-' || *p == '+' ) // If first character is a plus or minus sign + p++; // Skip over it + + double x = atoi ( p ); // ??? + + while ( isdigit ( (int) *p )) // If next character is a digit + p++; // Bump the pointer + + if ( *p == '.' ) // If it's a decimal point + { + double d = 1.0f; // "d" = '1' in floating point + p++; // Next character + + while ( isdigit ( (int) *p )) // As long as we see digits + { + d /= 10; // Divide "d" by 10 + x += d * ( *p - '0' ); // Then add in the next digit + p++; // Next character + } + } + + if ( *p == 'e' || *p == 'E' ) // Not a digit then is it an exponent? + { + p++; // Next character + int exp = atoi ( p ); // Convert exponent to integer + + while ( exp > 0 ) // If exponent positive + { + x *= 10; // Multiply the number by 10 + exp--; // And decrement the exponent + } + + while ( exp < 0 ) // If the exponent is negative + { + x /= 10; // Divide the number by 10 + exp++; // And increment the exponent + } + } + + if ( neg ) // If the whole answer is negative + x = -x; // Make the number negative + return x; // And send it back +} + + +/* + * "keypad_click" - + */ + +static int keypad_click ( int key ) +{ + int c = keypads[key].c; // Index to the "KP_Font" array + char fBuff[20]; // Frequency string + + if (( c >= KN_X && c <= KN_M )) // 'X', 'K' or 'M' key pressed? + { + int32_t scale = 1; // Default "scale"? + + if ( c >= KN_X && c <= KN_M ) // Redundent test? Why? + { + int n = c - KN_X; // 'X' -> 0, 'K' _> 1, 'M' -> 2 + + while ( n-- > 0 ) // Multiplier will be 1K or 1M + scale *= 1000; + } + + +/* + * When we get here, the numeric input has completed. We convert the number to + * a "double" and multiply it by the "scale". + */ + + if (kp_index ==0) // no entry - treat same as <- + return KP_CANCEL; + + double value = my_atof ( kp_buf ) * ( double ) scale; + + switch ( keypad_mode ) + { + case KM_START: // Start frequency entered? + SetSweepStart (( int32_t ) value ); + break; + + case KM_STOP: // Stop frequency entered? + SetSweepStop (( int32_t ) value ); + break; + + case KM_CENTER: // Center frequency entered? + SetSweepCenter (( int32_t ) value, WIDE ); + break; + + case KM_SPAN: // Frequency span entered? + SetSweepSpan (( int32_t ) value ); + break; + + case KM_FOCUS: // Focus frequency entered? + SetSweepCenter ((int32_t ) value, NARROW ); + break; + + case KM_REFPOS: // Reference level + SetRefLevel (value ); + break; + + case KM_ATTENUATION: // Attenuator setting + if ( value > PE4302_MAX ) // Too high? + { + DisplayError ( ERR_WARN, + "Illegal attenuator setting!", + "Ignored!", NULL, NULL ); + break; + } + + SetAttenuation ( value ); + break; + + case KM_ACTUALPOWER: // Power level + RequestSetPowerLevel ( value ); + break; + + case KM_IFFREQ: + + if ( !SetIFFrequency (( uint32_t ) value )) // Bad frequency? + { + strcpy ( fBuff, FormatFrequency (( uint32_t ) value )); + + DisplayError ( ERR_WARN, + fBuff, + "Invalid IF Frequency!", + "IF Frequency set to:", + FormatFrequency ( setting.IF_Freq )); + } + break; + + case KM_SGFREQ: + if ( (value > MAX_SIGLO_FREQ) || (value < MIN_SIGLO_FREQ) ) + { + strcpy ( fBuff, FormatFrequency (( uint32_t ) value )); + + DisplayError ( ERR_WARN, + fBuff, + "Invalid Frequency!", + NULL, + NULL ); + } + else + sigGenSetting.Frequency = value; + break; + + + case KM_SGLEVEL: + if ( (value > sigGenSetting.Calibration) || (value < sigGenSetting.Calibration - ATTENUATOR_RANGE) ) + { + + DisplayError ( ERR_WARN, + "Invalid Level!", + NULL, NULL, + NULL ); + } + else + sigGenSetting.Power = value; + break; + + + case KM_SGLEVCAL: + if ( (value > 20) || (value < -30) ) + { + + DisplayError ( ERR_WARN, + "Invalid Level!", + NULL, NULL, + NULL ); + } + else + sigGenSetting.Calibration = value; + break; + + + case KM_IFSTART: // IF Start frequency entered? + SetIFsweepStart (( int32_t ) value ); + break; + + + case KM_IFSTOP: // IF Stop frequency entered? + SetIFsweepStop (( int32_t ) value ); + break; + + case KM_IFSIG: // IF Signal frequency entered? + SetIFsweepSigFreq (( int32_t ) value ); + break; + + + case KM_PREAMP: + if (( value < 5 ) || ( value > 49 )) // Range check + { + DisplayError ( ERR_WARN, + "Illegal preamp gain setting!", + "Ignored!", NULL, NULL ); + break; + } + SetPreampGain ( value ); + break; + + case KM_TUNE: + if ( value > 255 ) // Illegal value? + { + DisplayError ( ERR_WARN, + "Illegal calibration factor!", + "Ignored!", NULL, NULL ); + break; // Done! + } + + if ( VFO == TX_4432 ) // Transmitter module selected? + { + xmit.Tune ( ( uint8_t ) value ); // Tune it + config.RX_capacitance = ( uint8_t ) value; // And save it + } + + else if ( VFO == RX_4432 ) // Receiver module selected? + { + rcvr.Tune ( ( uint8_t ) value ); // Tune it + config.RX_capacitance = ( uint8_t ) value; // And save it + } +#ifdef SI_TG_IF_CS + else if ( VFO == TGIF_4432 ) // Tracking generator IF module selected? + { + tg_if.Tune ( ( uint8_t ) value ); // Tune it + config.tgIF_capacitance = ( uint8_t ) value; // And save it + } +#endif +#ifdef SI_TG_LO_CS + else if ( VFO == TGLO_4432 ) // Tracking generator LO module selected? + { + tg_lo.Tune ( ( uint8_t ) value ); // Tune it + config.tgLO_capacitance = ( uint8_t ) value; // And save it + } +#endif + break; + + case KM_TGOFFSET: + if ( (value > MAX_TG_OFFSET) || (value < MIN_TG_OFFSET) ) + { + strcpy ( fBuff, FormatSignedFrequency (( int32_t ) value )); + + DisplayError ( ERR_WARN, + fBuff, + "Invalid Frequency!", + NULL, + NULL ); + } + else + trackGenSetting.Offset = value; + break; + + case KM_TGIF_DRIVE: + if ( (value > MAX_DRIVE) || (value < MIN_DRIVE) ) + { + DisplayError ( ERR_WARN, + "Invalid drive level!", + "(0-7)", + NULL, + NULL ); + } + else + trackGenSetting.IF_Drive = value; + break; + + case KM_TGLO_DRIVE: + if ( (value > MAX_DRIVE) || (value < MIN_DRIVE) ) + { + DisplayError ( ERR_WARN, + "Invalid drive level!", + "(0-7)", + NULL, + NULL ); + } + else + trackGenSetting.LO_Drive = value; + break; + + + case KM_BANDSCOPESTART: // Bandscope Start frequency entered? + SetBandscopeStart (( int32_t ) value ); + break; + + + case KM_BANDSCOPESPAN: // Bandscope span entered? + SetBandscopeSpan (( int32_t ) value ); + break; + + + } // End of "switch" + + return KP_DONE; // Indicate finished with the keypad + + } + + else if ( c <= 9 && kp_index < NUMINPUT_LEN ) + kp_buf[kp_index++] = '0' + c; + + else if ( c == KN_PERIOD && kp_index < NUMINPUT_LEN ) + { + +/* + * Check period in former input + */ + + int j; + + for ( j = 0; j < kp_index && kp_buf[j] != '.'; j++ ) {} + + if ( kp_index == j ) + kp_buf[kp_index++] = '.'; // Append period if there was no period + + } + + else if ( c == KN_MINUS ) + { + if ( kp_index == 0 ) + kp_buf[kp_index++] = '-'; + } + + else if ( c == KN_BS ) + { + if ( kp_index == 0 ) + return KP_CANCEL; + + --kp_index; + } + + kp_buf[kp_index] = '\0'; // NULL terminator + draw_numeric_input ( kp_buf ); + return KP_CONTINUE; // Still more to process +} + + +/* + * "keypad_apply_touch" - Figures out which keypad pad was touched + */ + +static int keypad_apply_touch ( void ) +{ + int touch_x, touch_y; // Touch coordinates + int i = 0; + + touch_position ( &touch_x, &touch_y ); // Get touch coordinates + + while ( keypads[i].x ) + { + if (keypads[i].x-2 < touch_x && touch_x < keypads[i].x+44+2 + && keypads[i].y-2 < touch_y && touch_y < keypads[i].y+44+2) + { + + selection = i; // Set focus + draw_keypad (); // Repaint with selected pad highlighted + touch_wait_release (); // Wait for pad released + selection = -1; // Erase focus + draw_keypad (); // And repaint again + return i; // Return selected pad index + } + + i++; // Try the next one + } // End of "while" loop + + if ( touch_y > 48 * 4 ) + return -2; // Exit keypad mode + + return -1; +} + + +/* + * "numeric_apply_touch" - + */ + +static void numeric_apply_touch ( void ) +{ + int touch_x, touch_y; // Touch coordinates + touch_position ( &touch_x, &touch_y ); // Get coordinates + + if ( touch_x < 64 ) // Out of touch area? + { // Hard-coded numbers have to go! + ui_mode_normal (); + return; + } + + if ( touch_x > 64 + 9 * 20 + 8 + 8 ) // Your guess is as good as mine! + { + ui_mode_keypad ( keypad_mode ); + ui_process_keypad (); + return; + } + + if ( touch_y > 240 - 40 ) + { + int n = 9 - ( touch_x - 64 ) / 20; + uistat.digit = n; + uistat.digit_mode = TRUE; + } + + else + { + int step, n; + + if ( touch_y < 100 ) + step = 1; + + else + step = -1; + + for (n = uistat.digit; n > 0; n-- ) + step *= 10; + + uistat.value += step; + } + + draw_numeric_area (); + touch_wait_release (); + uistat.digit_mode = FALSE; + draw_numeric_area (); + return; +} + + +/* + * "ui_process_keypad" - + */ + +static void ui_process_keypad ( void ) +{ + int status; + + kp_index = 0; + + while ( TRUE ) // "TRUE = '-1" in this program + { + status = touch_check (); // Look for screen touched + + if ( status == EVT_TOUCH_PRESSED ) // If it was touched + { + int key = keypad_apply_touch (); // Process it + + if ( key >= 0 && keypad_click ( key )) + break; // Exit loop on done or cancel + + else if ( key == -2 ) + { +// xxx; // ??????? +// return; + } + } + } + +//**HERE3 + + request_to_redraw_grid (); + ui_mode_normal (); +// draw_menu (); +// menu_move_back(); +} + + +/* + * "UiProcessTouch" - + */ + +void UiProcessTouch ( void ) +{ + int tstatus = touch_check(); // Look for screen touch + + if ( tstatus == EVT_TOUCH_PRESSED || tstatus == EVT_TOUCH_DOWN ) + { +// Serial.printf( "process Touch status = %i, UImode = %i \n", tstatus, ui_mode ); + + switch ( ui_mode ) + { + case UI_NORMAL: + +// Serial.println( "waiting for release" ); + int touch_x, touch_y; // Touch coordinates + touch_position ( &touch_x, &touch_y ); // Get coordinates + Serial.printf("x:%i y:%i\n", touch_x, touch_y); + touch_wait_release (); + + + // test to see if the touch is in the marker area + if ( (touch_y < 10) && (touch_x < 160) ) + { + marker[0].Toggle(); // marker 1 + return; + } + else if ( (touch_y < 10) && (touch_x > 160) ) + { + marker[2].Toggle(); // marker 3 + return; + } + else if ( (touch_y < 20) && (touch_x < 160) ) + { + marker[1].Toggle(); // marker 2 + return; + } + else if ( (touch_y < 20) && (touch_x > 160) ) + { + marker[3].Toggle(); // marker 4 + return; + } + else if ( (touch_y < 40) && (touch_x > 30) ) + StartMarkerMenu(); + else if ( (touch_y > 210) && ( setting.Mode == SA_LOW_RANGE ) ) + StartSweepMenu(); + else if ( (touch_x < 30) && (touch_y < 60) && ( setting.Mode == SA_LOW_RANGE ) ) + StartRBWMenu(); + else if ( (touch_x < 30) && (touch_y > CHAR_HEIGHT * 20 ) && ( touch_y < CHAR_HEIGHT * 22 ) && ( setting.Mode == SA_LOW_RANGE ) ) + { + SetSpur (!setting.Spur); + return; + } + else if ( (touch_x < 30) && ( setting.Mode == SA_LOW_RANGE ) ) + StartDisplayMenu(); + + selection = -1; // Switch menu mode + bg = BLACK; // black background + ui_mode_menu (); + break; + + case UI_MENU: + menu_apply_touch (); + break; + + case UI_NUMERIC: + numeric_apply_touch (); + break; + } + } + +} + + +/* + * Function to enter menu at certain level and selection + * HERE + * static uint8_t menu_current_level = 0; // Current level (MAIN) + * + * static Menuitem *menu_stack[MENU_STACK_DEPTH] = // Initialize the stack + * { menu_top, // Main menu is on top + * NULL, // These get set as we + * NULL, // go along + * NULL + * }; + * + * // helpers for setting menus from outside + * enum { MENU_TOP, MENU_SWEEP, MENU_DISPLAY, MENU_STORAGE, MENU_OUTPUT, MENU_SAVE, MENU_CONFIG }; // Root Menu + * enum { SWEEP_START, SWEEP_STOP, SWEEP_CENTRE, SWEEP_SPAN, FOCUS }; // Sweep menu - level 1 + * enum { RBW, ATTEN, MARKERS }; // Sweep more menu - level 2 + * enum { TRACES, PREAMP, REF_LEVEL, DB_DIV, SPUR, DEFAULT_SETTINGS }; // Display menu - level 1 + * + */ + +void StartSigGenMenu ( void ) +{ + tft.unloadFont(); + selection = -1; // Switch menu mode + menu_current_level = 0; + menu_stack[0] = menu_sig_gen; + bg = SIG_BACKGROUND_COLOR; + tft.fillRect ( 320-60, 0, 60, 32*ELEMENTS(menu_sig_gen), bg ); + ui_mode_menu (); +} + +void StartSigGenFreq ( void ) +{ + tft.unloadFont(); + selection = -1; // Switch menu mode +// menu_current_level = 0; +// menu_stack[0] = menu_sig_gen; + bg = SIG_BACKGROUND_COLOR; + tft.fillScreen ( bg ); + menu_sig_freq_cb ( 0 ); +} + + + +static void StartMarkerMenu ( void ) +{ + selection = -1; // Switch menu mode + menu_current_level = 3; + menu_stack[1] = menu_sweep; + menu_stack[2] = menu_sweep2; + menu_stack[3] = menu_markers; +} + + +static void StartSweepMenu ( void ) +{ + selection = -1; // Switch menu mode + menu_current_level = 1; + menu_stack[1] = menu_sweep; + menu_stack[2] = NULL; + menu_stack[3] = NULL; +} + +static void StartDisplayMenu ( void ) +{ + selection = -1; // Switch menu mode + menu_current_level = 1; + menu_stack[1] = menu_display; + menu_stack[2] = NULL; + menu_stack[3] = NULL; +} + +static void StartRBWMenu ( void ) +{ + selection = -1; // Switch menu mode + menu_current_level = 3; + menu_stack[1] = menu_sweep; + menu_stack[2] = menu_sweep2; + menu_stack[3] = menu_rbw; +} + + +/* + * Resets the menu stack to root level for SA mode + */ +void ResetSAMenuStack (void) +{ + tft.unloadFont(); + selection = -1; // Switch menu mode + menu_current_level = 0; + menu_stack[0] = menu_top; + menu_stack[1] = NULL; + menu_stack[2] = NULL; + menu_stack[3] = NULL; + if (ui_mode != UI_NORMAL) + ui_mode_normal (); +} + + + +/* + * Resets the menu stack to root level for IF_SWEEP mode + */ +void ResetIFsweepMenuStack (void) +{ + tft.unloadFont(); + selection = -1; // Switch menu mode + menu_current_level = 0; + menu_stack[0] = menu_IFsweep_top; + menu_stack[1] = NULL; + menu_stack[2] = NULL; + menu_stack[3] = NULL; + if (ui_mode != UI_NORMAL) + ui_mode_normal (); +} + + + +/* + * Resets the menu stack to root level for Bandscope mode + */ +void ResetBandscopeMenuStack (void) +{ + tft.unloadFont(); + selection = -1; // Switch menu mode + menu_current_level = 0; + menu_stack[0] = menu_Bandscope_top; + menu_stack[1] = NULL; + menu_stack[2] = NULL; + menu_stack[3] = NULL; + if (ui_mode != UI_NORMAL) + ui_mode_normal (); +} + + +/* + * "ui_process" - + */ + +void ui_process ( void ) +{ + switch ( operation_requested ) // Only one case??? + { + case OP_TOUCH: + UiProcessTouch (); + break; + } + + operation_requested = OP_NONE; +} diff --git a/ui.h b/ui.h new file mode 100644 index 0000000..6e404c6 --- /dev/null +++ b/ui.h @@ -0,0 +1,32 @@ +/* + * "ui.h" + * + * This file contains the definitions of things related to the TinySA touch screen + * interface. + */ + +#include // 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