commit b04a218586ab6d0ab854bbbc659fe2ae4a435261 Author: DPWilde Date: Sat Aug 15 19:03:43 2020 +0100 Add files via upload 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