diff --git a/Bandscope.ino b/Bandscope.ino index 48acdd5..5ee0bf5 100644 --- a/Bandscope.ino +++ b/Bandscope.ino @@ -1,9 +1,23 @@ +uint32_t colourTest; + /* * Initialise variables and SI4432 for the low frequency sweep */ void initBandscope() { + // set up checkerboard sizes + waterfallHeight = WATERFALL_HEIGHT; + gridHeight = SCREEN_HEIGHT - waterfallHeight - 10; + gridWidth = SCREEN_WIDTH; + yGrid = Y_GRID; // no of grid divisions + yDelta = gridHeight / yGrid; // no of points/division + xGrid = X_GRID; + xOrigin = 0; + yOrigin = 0; + displayPoints = setting.BandscopePoints; + xDelta = SCREEN_WIDTH / xGrid; + ClearDisplay (); /* * Set up the "img" Sprite. This is the image for the graph. It makes for faster display @@ -22,25 +36,25 @@ void initBandscope() 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 + img.createSprite ( 2, gridHeight + 1 ); // Only 2 columns wide /* - * The "tSprite" is used for displaying the data above the scan grid. + * The "tSprite" is used for displaying the data above the scan grid - we don't use it in this mode + * The "sSprite" is for displaying the sidebar stuff, but reused here for the waterfall */ 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(); + sSprite.setColorDepth (16); // we don't need 16bit but its faster + sSprite.setAttribute ( PSRAM_ENABLE, false ); // Don't use the PSRAM on the WROVERs + sSprite.createSprite ( gridWidth, waterfallHeight ); // Full width + sSprite.setScrollRect(0, 0, gridWidth, waterfallHeight, TFT_BLACK); /* * Create and draw the sprite for the gain scale */ - CreateGainScale (); + CreateGridScale (); // Make sure everything will be reset old_settingAttenuate = -1000; @@ -65,9 +79,7 @@ void initBandscope() 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"); } @@ -75,9 +87,37 @@ void 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 + * The display is split and shows a waterfall. + * The number of points is reduced, and frequency change is done using an offset to allow the + * delay time between changing frequency and taking a reading to be reduced. + * + * Frequency scheme: + * When the LO frequency is < 480MHz the LO can be adjusted +- 80kHz from the + * nominal frequency in 156.25Hz steps, ie +- 512 steps. + * Actually this is not possible! +-511 steps is + * + * + * If the LO is above 480MHz the the adjustment is +-160kHz in 312.5Hz steps. + * If the IF is 434MHz then 480MHz -> 46Mhz for the signal being analysed, so fine + * for most of the HF bands. + * + * In bandscope mode the RBW is fixed at the minimum 2.6kHz, span at 200kHz and + * there are 80 data points + * + * 200kHz -> 2.5kHz steps between each reading or 16 * 156.25Hz if in low band + * + * Start by setting the LO to the frequency for the start of the sweep plus 80kHz + * and set the offset value at -80kHz. + * At each reading increment the offset value by 16 (8 in high band). + * In this mode the delay time between reading is set at a shorter value than + * normally used by the RBW as the LO does not turn off at each change in offset, unlike + * a normal frequency change. + * When the offset value reaches +80kHz then we need to reset the LO (using normal delaytime) + * and continue until we get to the end of the sweep. + * + * Due to the limitation of not being able to do +-512, or +-16 stesp, we will use +-31 steps per frequency + * jump instead + * */ @@ -87,12 +127,10 @@ 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 unsigned long bandscopeDelay; -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 @@ -100,14 +138,7 @@ 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 minRSSI = 255; // 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 @@ -134,20 +165,17 @@ static uint16_t chunkIndex; { 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; + offsetFreqIncrement = autoSweepFreqStep; // 2500 Hz for 200kHz span, 80 points per sweep - 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 + bandwidth = rcvr.SetRBW ( setting.BandscopeRBW10, &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 +// sweepFreqStep = autoSweepFreqStep; // Step for each reading if ( setting.Attenuate != old_settingAttenuate ) { @@ -158,6 +186,9 @@ static uint16_t chunkIndex; resetAverage = changedSetting; + maxGrid = setting.BandscopeMaxGrid; + minGrid = setting.BandscopeMinGrid; + #ifdef USE_WIFI // Vary number of points to send in each chunk depending on delaytime @@ -167,28 +198,34 @@ static uint16_t chunkIndex; wiFiPoints = MAX_WIFI_POINTS; if (wiFiPoints > setting.BandscopePoints) wiFiPoints = setting.BandscopePoints; - Serial.printf("No of wifiPoints set to %i\n", wiFiPoints); +// 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 { } + resetOffsets(); + + // set the offset value in the SI4432 + xmit.SetOffset(offsetValue); + 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 + + // set the LO frequency (offsetFreq is -ve at start!) + uint32_t xmitFreq = setting.IF_Freq + autoSweepFreq - offsetFreq; +// Serial.printf("XmitFreq %i\n", xmitFreq); + xmit.SetFrequency ( xmitFreq ); + // delay will vary depending on whether or not the nominal frequency is changed + bandscopeDelay = delaytime; // long delay #ifdef USE_WIFI @@ -212,49 +249,16 @@ static uint16_t chunkIndex; #endif // #ifdef USE_WIFI - sweepStep = 0; +// 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 ); +// 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 + minRSSI = 255; // real value should always be less - - - pointsPastPeak = 0; // Avoid possible peak detection at start of sweep - peakRSSI = 0; + DisplayBandscopeInfo (); // Display axis and other info sweepStartDone = true; // Make sure this initialize is only done once per sweep initSweep = false; @@ -282,20 +286,17 @@ static uint16_t chunkIndex; */ nowMicros = micros(); - while (( nowMicros - setFreqMicros ) < delaytime ) + while (( nowMicros - setFreqMicros ) < bandscopeDelay ) { 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"); + webSocket.loop (); // Check websockets - includes Yield() to allow other tasks to run 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(); } @@ -311,29 +312,44 @@ static uint16_t chunkIndex; * The second one produces a listing more fit for human consumption! */ -// if ( showRSSI ) // Displaying RSSI? -// { + 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 + autoSweepFreq += autoSweepFreqStep; // Increment the frequency + autoSweepStep++; // and increment the step count + offsetFreq += offsetFreqIncrement; + offsetValue += offsetIncrement; /* - * Change the transmitter frequency for the next reading and record the time for + * Change the local oscillator 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 + + if (offsetValue >= 512) // reached offset limits + { + resetOffsets(); + xmit.SetOffset(offsetValue); + uint32_t f = setting.IF_Freq + autoSweepFreq - offsetFreq; +// Serial.printf("Sweep setFreq %i\n", f); + xmit.SetFrequency ( f ); // Set the new LO frequency as soon as RSSI read + bandscopeDelay = delaytime; + } + else + { + xmit.SetOffset(offsetValue); + bandscopeDelay = offsetDelayTime; + } #ifdef USE_WIFI @@ -376,7 +392,7 @@ static uint16_t chunkIndex; chunkIndex = 0; jsonDocument.clear(); jsonDocument["mType"] = "chunkSweep"; - jsonDocument["StartIndex"] = sweepStep; + jsonDocument["StartIndex"] = autoSweepStep; jsonDocument["sweepPoints"] = sweepPoints; jsonDocument["sweepTime"] = (uint32_t)(sweepMicros/1000); Points = jsonDocument.createNestedArray ("Points" ); // Add Points array @@ -386,164 +402,110 @@ static uint16_t chunkIndex; #endif // #ifdef USE_WIFI - myActual[autoSweepStep] = rxRSSI; + if (rxRSSI < minRSSI) // Detect minimum for sweep + minRSSI = rxRSSI; - myGain[autoSweepStep] = gainReading; - DrawCheckerBoard ( oldSweepStep ); // Draw the grid for the point in the sweep we have just read + uint16_t pixelsPerPoint = SCREEN_WIDTH / displayPoints; + for (uint16_t i = 0; i< pixelsPerPoint; i++) { + + uint16_t tmp = oldSweepStep * pixelsPerPoint + i; + + myActual[tmp] = rxRSSI; + + myGain[tmp] = gainReading; + + DrawCheckerBoard ( tmp ); // Draw the grid if ( resetAverage || setting.Average == AV_OFF ) // Store data, either as read or as rolling average - myData[oldSweepStep] = myActual[oldSweepStep]; - + myData[tmp] = myActual[oldSweepStep]; else { switch ( setting.Average ) { case AV_MIN: - if ( myData[oldSweepStep] > myActual[oldSweepStep] ) - myData[oldSweepStep] = myActual[oldSweepStep]; + if ( myData[tmp] > myActual[oldSweepStep] ) + myData[tmp] = myActual[oldSweepStep]; break; - + case AV_MAX: - if ( myData[oldSweepStep] < myActual[oldSweepStep] ) - myData[oldSweepStep] = myActual[oldSweepStep]; + if ( myData[tmp] < myActual[oldSweepStep] ) + myData[tmp] = myActual[oldSweepStep]; break; - + case AV_2: - myData[oldSweepStep] = ( myData[oldSweepStep] + myActual[oldSweepStep] ) / 2; + myData[tmp] = ( myData[tmp] + myActual[oldSweepStep] ) / 2; break; - + case AV_4: - myData[oldSweepStep] = ( myData[oldSweepStep]*3 + myActual[oldSweepStep] ) / 4; + myData[tmp] = ( myData[tmp]*3 + myActual[oldSweepStep] ) / 4; break; - + case AV_8: - myData[oldSweepStep] = ( myData[oldSweepStep]*7 + myActual[oldSweepStep] ) / 8; + myData[tmp] = ( myData[tmp]*7 + myActual[oldSweepStep] ) / 8; break; } - DisplayPoint ( myData, oldSweepStep, AVG_COLOR ); + DisplayPoint ( myData, tmp, AVG_COLOR ); } - + if ( setting.ShowSweep ) - DisplayPoint ( myActual, oldSweepStep, DB_COLOR ); - + DisplayPoint ( myActual, tmp, DB_COLOR ); + if ( setting.ShowGain ) - displayGainPoint ( myGain, oldSweepStep, GAIN_COLOR ); - + displayGainPoint ( myGain, tmp, 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] ) + DisplayPoint ( myStorage, tmp, STORAGE_COLOR ); + + // If in the first few points show the scale + if ( ( tmp < 4 * CHAR_WIDTH ) && (tmp > 0) ) { - 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 + int16_t scaleX = -tmp + 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 ( tmp > 0 ) // Only push if not first point (two pixel wide img) + img.pushSprite ( xOrigin + tmp - 1 , yOrigin ); - if ( oldSweepStep > 0 ) // Only push if not first point (two pixel wide img) - img.pushSprite ( X_ORIGIN+oldSweepStep-1, Y_ORIGIN ); +/* + * put data into the top row of the waterfall + * 16 bit colours have 5 bits for Red, 6 bits for Green, 5 bits for Blue + * We will just change the green level here for first test + */ + uint32_t level = (uint32_t)( (float)(rxRSSI - setting.WaterfallMin) * setting.WaterfallGain) ; // testing colours + if (rxRSSI < setting.WaterfallMin) + level = 0; + uint32_t green = level; + uint32_t red = 0; + uint32_t blue = 0; + if (green > 63) + { + green = 63; + red = level - 63; + if ( red > 31 ) + { + red = 31; + blue = level - 63 - 31; + if ( blue > 31 ) + blue = 31; + } + } + + uint32_t pixelColour = (red << 11) + (green << 5) + blue; + + if (colourTest > 0) // delete at some stage + pixelColour = colourTest; + +// Serial.printf("rxRSSI %i; red %i; green %i; blue %i; colour %i \n", rxRSSI, red, green, blue, pixelColour); + sSprite.drawPixel ( tmp, 0, pixelColour ); + + } - myFreq[oldSweepStep] = oldSweepFreq; // Store the frequency for XML file creation + myFreq[oldSweepStep] = oldSweepFreq; // Store the frequency for XML file creation - if ( sweepStep >= sweepPoints ) // If we have got to the end of the sweep + if ( autoSweepStep >= sweepPoints ) // If we have got to the end of the sweep { // autoSweepStep = 0; sweepStartDone = false; @@ -552,8 +514,6 @@ static uint16_t chunkIndex; 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 @@ -565,7 +525,6 @@ static uint16_t chunkIndex; showRSSI = 0; // Then turn it off #ifdef USE_WIFI - if (( numberOfWebsocketClients > 0) && jsonDocInitialised && (chunkIndex > 0) ) { String wsBuffer; @@ -579,8 +538,147 @@ static uint16_t chunkIndex; else Serial.println ( "No buffer :("); } - #endif // #ifdef USE_WIFI - } // End of "if ( sweepStep >= sweepPoints )" -} // End of "doSweepLow" + // scroll the waterfall down one pixel + sSprite.scroll( 0, 1 ); + sSprite.pushSprite( 0, gridHeight + 1 ); + + } // End of "if ( autoSweepStep >= sweepPoints )" + +} // End of "doBandscope" + + +/* + * "DisplayBandscopeInfo" - Draws the frequency info below the checkerboard. Called + * when a setting is changed to set axis labels + */ + +void DisplayBandscopeInfo () +{ +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 frequency labels at bottom if changed + */ + + tft.setTextColor ( WHITE,BLACK ); + tft.setTextSize ( 1 ); + + fStart = (double)( setting.BandscopeStart / 1000000.0 ); // Start freq + fCenter = (double)( ( setting.BandscopeStart + setting.BandscopeSpan/2.0 ) / 1000000.0 ); + fStop = (double)( (setting.BandscopeStart + setting.BandscopeSpan ) / 1000000.0 ) ; // Stop freq + + if ( old_startFreq != fStart || old_stopFreq != fStop ) + { +// Serial.printf("DisplayBandscopeInfo fStart %f; old_startFreq %f \n", fStart, old_startFreq); +// Serial.printf("DisplayBandscopeInfo fStop %f; old_stopFreq %f \n", fStop, old_stopFreq); + + tft.fillRect ( xOrigin, SCREEN_HEIGHT - + CHAR_HEIGHT, SCREEN_WIDTH - xOrigin - 1, SCREEN_HEIGHT - 1, BLACK ); + + // Show operating mode + tft.setCursor ( xOrigin + 50, SCREEN_HEIGHT - CHAR_HEIGHT ); + tft.setTextColor ( DB_COLOR ); + tft.printf ( "Mode:%s", modeText[setting.Mode] ); + tft.setTextColor ( WHITE ); + + tft.setCursor ( xOrigin + 2, SCREEN_HEIGHT - CHAR_HEIGHT ); + + tft.print ( fStart ); + + tft.setCursor ( SCREEN_WIDTH - 25, SCREEN_HEIGHT - CHAR_HEIGHT ); + tft.print ( fStop ); + + +/* + * Show the center frequency: + */ + tft.setCursor ( SCREEN_WIDTH / 2 - 20 + xOrigin, 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 ); + + +/* + * We use the "tSprite" to paint the data at the top of the screen to avoid + * flicker. + */ + + +/* + * 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 ( xOrigin, 0 ); // Write sprite to the display + + updateSidebar = false; +} // End of "DisplayBandscopeInfo" + + +void resetOffsets () +{ + if (setting.BandscopeStart < 480000000) // low range. Assume never change range mid sweep! + { + offsetStep = -31; + offsetIncrement = 16; // 16 * 156.25 = 2500 + } + else // high range + { + offsetStep = -63; + offsetIncrement = 8; // 8 * 312.5 = 2500 + } + + if (setting.BandscopeSpan == 400000) // wider span, same no of points + offsetIncrement = offsetIncrement * 2; + + + offsetFreq = offsetStep * offsetFreqIncrement; + offsetValue = offsetStep * offsetIncrement; +} diff --git a/IFsweep.ino b/IFsweep.ino index ca7607e..0aecf39 100644 --- a/IFsweep.ino +++ b/IFsweep.ino @@ -8,6 +8,16 @@ */ void initIF_Sweep() { + // set up checkerboard sizes + gridHeight = GRID_HEIGHT; + gridWidth = DISPLAY_POINTS; + yGrid = Y_GRID; // no of grid divisions + yDelta = gridHeight / yGrid; // no of points/division + xGrid = X_GRID; + xOrigin = X_ORIGIN; + yOrigin = Y_ORIGIN; + displayPoints = DISPLAY_POINTS; + xDelta = displayPoints / xGrid; init_sweep(); @@ -62,10 +72,10 @@ static uint16_t chunkIndex; 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; + autoSweepFreqStep = ( stopFreq_IF - startFreq_IF ) / displayPoints; vbw = autoSweepFreqStep / 1000.0; // Set the video resolution -// ownrbw = ((float) ( stopFreq_IF - startFreq_IF )) / DISPLAY_POINTS / 1000.0; // kHz +// ownrbw = ((float) ( stopFreq_IF - startFreq_IF )) / displayPoints / 1000.0; // kHz // // if ( ownrbw < 2.6 ) // If it's less than 2.6KHz // ownrbw = 2.6; // set it to 2.6KHz @@ -81,7 +91,7 @@ static uint16_t chunkIndex; 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 + sweepPoints = displayPoints; // At least the right number of points for the display sweepFreqStep = ( stopFreq_IF - startFreq_IF ) / sweepPoints; // Step for each reading @@ -89,14 +99,17 @@ static uint16_t chunkIndex; xmit.SetPowerReference ( setting.ReferenceOut ); // Set the GPIO reference output + maxGrid = setting.MaxGrid; + minGrid = setting.MinGrid; + #ifdef USE_WIFI // Vary number of points to send in each chunk depending on delaytime // A chunk is sent at the end of each sweep regardless wiFiPoints = wiFiTargetTime / delaytime; if (wiFiPoints > MAX_WIFI_POINTS) wiFiPoints = MAX_WIFI_POINTS; - if (wiFiPoints > DISPLAY_POINTS*OVERLAP) - wiFiPoints = DISPLAY_POINTS*OVERLAP; + if (wiFiPoints > displayPoints*OVERLAP) + wiFiPoints = displayPoints*OVERLAP; // Serial.printf("No of wifiPoints set to %i\n", wiFiPoints); pushIFSweepSettings(); @@ -445,15 +458,15 @@ static uint16_t chunkIndex; } // If in the last few points and gain trace is displayed show the gain scale - if ( setting.ShowGain && (oldSweepStep > DISPLAY_POINTS - 2 * CHAR_WIDTH) ) + if ( setting.ShowGain && (oldSweepStep > displayPoints - 2 * CHAR_WIDTH) ) { - int16_t scaleX = DISPLAY_POINTS - 2 * CHAR_WIDTH - oldSweepStep + 1; // relative to the img sprite + int16_t scaleX = displayPoints - 2 * CHAR_WIDTH - oldSweepStep + 1; // relative to the img sprite img.setPivot( scaleX, 0); gainScaleSprite.pushRotated ( &img, 0, TFT_BLACK ); // Send the sprite to the target sprite, with transparent colour } if ( oldSweepStep > 0 ) // Only push if not first point (two pixel wide img) - img.pushSprite ( X_ORIGIN+oldSweepStep-1, Y_ORIGIN ); + img.pushSprite ( xOrigin+oldSweepStep-1, yOrigin ); myFreq[oldSweepStep] = oldSweepFreq; // Store the frequency for XML file creation @@ -469,11 +482,11 @@ static uint16_t chunkIndex; 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 + if ( myActual[displayPoints-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; + myActual[displayPoints-1] = rxRSSI; // Yes, save it + myGain[displayPoints-1] = gainReading; + myFreq[displayPoints-1] = oldSweepFreq; } if ( showRSSI == 1 ) // Only show it once? diff --git a/RXsweep.ino b/RXsweep.ino new file mode 100644 index 0000000..2857202 --- /dev/null +++ b/RXsweep.ino @@ -0,0 +1,509 @@ + +/* + * RX sweep fixes the LO and sweeps the receiver from a start value to an stop value to + * measure the RX FIR bandpass pass filter response. This eleimates any effect from the SAW + * and low pass filters before the receiver. + * It requires a fixed strength and frequency signal, and this is provided + * by setting the reference output to say 15MHz. Avoid 30Mhz due to some stray clock signal + * The input should be connected to the reference signal output with an external cable + */ +void initRX_Sweep() +{ + // set up checkerboard sizes + gridHeight = GRID_HEIGHT; + gridWidth = DISPLAY_POINTS; + yGrid = Y_GRID; // no of grid divisions + yDelta = gridHeight / yGrid; // no of points/division + xGrid = X_GRID; + xOrigin = X_ORIGIN; + yOrigin = Y_ORIGIN; + displayPoints = DISPLAY_POINTS; + xDelta = displayPoints / xGrid; + + init_sweep(); + SetRXsweepSpan (setting.Bandwidth10 * 1000); // 10 * bandwidth + + tinySA_mode = RX_SWEEP; + setting.Mode = tinySA_mode; + + ResetRXsweepMenuStack(); // Put menu stack back to root level + +} + + +void doRX_Sweep() +{ +static uint32_t autoSweepStep = 0; +static uint32_t autoSweepFreq = 0; +static uint32_t autoSweepFreqStep = 0; +static uint32_t nextPointFreq = 0; // Frequency for the next display point. Used for substeps +static unsigned long setFreqMicros; +static unsigned long nowMicros; + +static 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 RXSweep or changedSetting"); + autoSweepFreqStep = ( stopFreq_RX - startFreq_RX ) / displayPoints; + + vbw = autoSweepFreqStep / 1000.0; // Set the video resolution + +/* + * Use the RBW setting. If zero then it is meaningless, use smallest instead + */ + bandwidth = rcvr.SetRBW ( setting.Bandwidth10, &delaytime ); // Set it in the receiver Si4432. delaytime is returned + + sweepPoints = displayPoints; + + sweepFreqStep = ( stopFreq_RX - startFreq_RX ) / sweepPoints; // Step for each reading + + att.SetAtten ( 0 ); // Set the internal attenuator + + xmit.SetPowerReference ( setting.ReferenceOut ); // Set the GPIO reference output + + maxGrid = setting.MaxGrid; + minGrid = setting.MinGrid; + +#ifdef USE_WIFI + // Vary number of points to send in each chunk depending on delaytime + // A chunk is sent at the end of each sweep regardless + wiFiPoints = wiFiTargetTime / delaytime; + if (wiFiPoints > MAX_WIFI_POINTS) + wiFiPoints = MAX_WIFI_POINTS; + if (wiFiPoints > displayPoints*OVERLAP) + wiFiPoints = displayPoints*OVERLAP; +// Serial.printf("No of wifiPoints set to %i\n", wiFiPoints); + + pushIFSweepSettings(); +#endif // #ifdef USE_WIFI + + + } // initSweep || changedSetting + + autoSweepStep = 0; // Set the step counter to zero + autoSweepFreq = startFreq_RX; // Set the start frequency. + + nextPointFreq = autoSweepFreq + autoSweepFreqStep; + + + while (( micros() - setFreqMicros ) < delaytime ) // Make sure enough time has elasped since previous frequency write + { + } + + //Serial.printf("set rcvr Freq get:%u, tempIF:%u\n", rcvr.GetFrequency(), tempIF); + rcvr.SetFrequency ( autoSweepFreq ); // Set the RX Si4432 to the start of the sweep + + setFreqMicros = micros(); // Store the time the frequency was changed + xmit.SetFrequency ( sigFreq_RX + setting.IF_Freq ); // set the LO frequency to the IF plus reference + +// Serial.printf("autoSweepFreq init: %u\n", autoSweepFreq); + + +#ifdef USE_WIFI + + if ( numberOfWebsocketClients > 0 ) // Start off the json document for the scan + { + 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_RX; // Start freq for the RX + stopFreq = stopFreq_RX; // Stop freq for the RX + + pointMinGain = 100; // Reset min/max values + pointMaxRSSI = 0; + + DisplayInfo (); // Display axis, top and side bar text + + peakLevel = 0; // Reset the peak values for the sweep + peakFreq = 0.0; + peakGain = 100; // Set to higher than gain can ever be + + lastMinRSSI = minRSSI; + minRSSI = 300; // Higher than it can be + + +/* +* Copy the values for the peaks (marker positions) to the old versions. No need to +* reset the indicies or frequencies; just the "Level". +*/ + + for ( int i = 0; i < MARKER_COUNT; i++ ) + { + oldPeaks[i].Level = peaks[i].Level; + oldPeaks[i].Index = peaks[i].Index; + oldPeaks[i].Freq = peaks[i].Freq; + peaks[i].Level = 0; + } + + pointsPastPeak = 0; // Avoid possible peak detection at start of sweep + peakRSSI = 0; + + sweepStartDone = true; // Make sure this initialize is only done once per sweep + initSweep = false; + changedSetting = false; + + lastSweepStartMicros = sweepStartMicros; // Set last time we got here + sweepStartMicros = micros(); // Current time + sweepMicros = sweepStartMicros - lastSweepStartMicros; // Calculate sweep time (no rollover handling) + + } // End of "if ( !sweepStartDone ) || initSweep || changedSetting )" + + +/* +* Here we do the actual sweep. Save the current step and frequencies for the next time +* through, then wait the required amount of time based on the RBW before taking the +* signal strength reading and changing the transmitter (LO) frequency. +*/ + + uint16_t oldSweepStep = autoSweepStep; + uint32_t oldSweepFreq = autoSweepFreq; + + /* + * Wait until time to take the next reading. If a long wait then check the touchscreen + * and Websockets while we are waiting to improve response + */ + nowMicros = micros(); + + while (( nowMicros - setFreqMicros ) < delaytime ) + { + + if ( ( nowMicros - setFreqMicros + delaytime > 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. + * + * Not we also set the lo here to simulate the effect on the filters in a real sweep + */ + + setFreqMicros = micros(); // Store the time the LO frequency was changed + xmit.SetFrequency ( sigFreq_RX + setting.IF_Freq ); // set the LO frequency to the IF plus reference + rcvr.SetFrequency ( autoSweepFreq ); // Set the RX Si4432 to the IF frequency + + // Serial.printf("Step: %i Required: %i Actual %i\n", sweepStep, autoSweepFreq, rcvr.GetFrequency()); + +#ifdef USE_WIFI + + if ( numberOfWebsocketClients > 0 ) + { + if ( jsonDocInitialised ) + { + JsonObject dataPoint = Points.createNestedObject (); // Add an object to the Json array to be pushed to the client + dataPoint["x"] = oldSweepFreq/1000000.0; // Set the x(frequency) value + dataPoint["y"] = rxRSSI; // Set the y (RSSI) value + // Serial.printf ( "Add point chunkIndex %u, sweepStep %u of %u \n", chunkIndex, sweepStep, sweepPoints); + chunkIndex++; // increment no of data points in current WiFi chunk + + if ( chunkIndex >= wiFiPoints ) // Send the chunk of data and start new jSon document + { + String wsBuffer; + + if ( wsBuffer ) + { + // Serial.print("D"); + serializeJson ( jsonDocument, wsBuffer ); + // Serial.printf("J%u", wsBuffer.length() ); + unsigned long s = millis(); + webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients + if (millis() - s > 1000) + { + Serial.println("webSocketTimeout"); + Serial.println(wsBuffer); + numberOfWebsocketClients = 0; + } + // Serial.print("j"); + } + + else + Serial.println("No buffer :("); + } + } + if ( ( chunkIndex >= wiFiPoints ) || !jsonDocInitialised ) // Start new jSon document + { + chunkIndex = 0; + 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 > displayPoints - 2 * CHAR_WIDTH) ) + { + int16_t scaleX = displayPoints - 2 * CHAR_WIDTH - oldSweepStep + 1; // relative to the img sprite + img.setPivot( scaleX, 0); + gainScaleSprite.pushRotated ( &img, 0, TFT_BLACK ); // Send the sprite to the target sprite, with transparent colour + } + + if ( oldSweepStep > 0 ) // Only push if not first point (two pixel wide img) + img.pushSprite ( xOrigin+oldSweepStep-1, yOrigin ); + + myFreq[oldSweepStep] = oldSweepFreq; // Store the frequency for XML file creation + + } // End of "if ( autoSweepFreq >= nextPointFreq )" + + if ( sweepStep >= sweepPoints ) // If we have got to the end of the sweep + { +// autoSweepStep = 0; + sweepStartDone = false; + + if ( sweepCount < 2 ) + sweepCount++; // Used to disable wifi at start + + oldPeakLevel = peakLevel; //Save value of peak level for use by the "SetPowerLevel" function + + if ( myActual[displayPoints-1] == 0 ) // Ensure a value in last data point + { + myActual[displayPoints-1] = rxRSSI; // Yes, save it + myGain[displayPoints-1] = gainReading; + myFreq[displayPoints-1] = oldSweepFreq; + } + + if ( showRSSI == 1 ) // Only show it once? + showRSSI = 0; // Then turn it off + +#ifdef USE_WIFI + + if (( numberOfWebsocketClients > 0) && jsonDocInitialised && (chunkIndex > 0) ) + { + String wsBuffer; + + if (wsBuffer) + { + serializeJson ( jsonDocument, wsBuffer ); + webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients + } + + else + Serial.println ( "No buffer :("); + } + +#endif // #ifdef USE_WIFI + + } // End of "if ( sweepStep >= sweepPoints )" + + +} diff --git a/SigLo.ino b/SigLo.ino index a834584..d1a5f99 100644 --- a/SigLo.ino +++ b/SigLo.ino @@ -17,11 +17,14 @@ void initSigLow() //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.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 + + xmit.SetOffset ( 0 ); // make sure frequency offset registers are zero + rcvr.SetOffset ( 0 ); #ifdef SI_TG_IF_CS if (tgIF_OK) { @@ -47,7 +50,7 @@ void initSigLow() setting.Mode = tinySA_mode; tft.unloadFont(); - tft.setCursor ( X_ORIGIN + 50, SCREEN_HEIGHT - CHAR_HEIGHT ); + tft.setCursor ( xOrigin + 50, SCREEN_HEIGHT - CHAR_HEIGHT ); tft.setTextSize(1); tft.setTextColor ( YELLOW ); tft.printf ( "Mode:%s", modeText[setting.Mode] ); diff --git a/SweepLo.ino b/SweepLo.ino index 17b0e26..cc67e7e 100644 --- a/SweepLo.ino +++ b/SweepLo.ino @@ -4,6 +4,17 @@ */ void initSweepLow() { + // set up checkerboard sizes + gridHeight = GRID_HEIGHT; + gridWidth = DISPLAY_POINTS; + yGrid = Y_GRID; // no of grid divisions + yDelta = gridHeight / yGrid; // no of points/division + xGrid = X_GRID; + xOrigin = X_ORIGIN; + yOrigin = Y_ORIGIN; + displayPoints = DISPLAY_POINTS; + xDelta = displayPoints / xGrid; + init_sweep(); tinySA_mode = SA_LOW_RANGE; @@ -47,6 +58,9 @@ static uint16_t peakRSSI; static uint16_t prevPointRSSI; static uint32_t peakFreq; static uint16_t peakIndex; +static uint16_t maxRSSI; +static uint32_t maxFreq; +static uint16_t maxIndex; static uint16_t pointsPastPeak; static uint16_t pointsPastDip; static uint16_t minRSSI; // Minimum level for the sweep @@ -56,6 +70,7 @@ static bool resetAverage; // Flag to indicate a setting has changed and avera static bool jsonDocInitialised = false; static uint16_t chunkIndex; +static uint32_t offsetIF; // IF frequency offset by half the bandwidth to position in the centre of the filter @@ -77,7 +92,7 @@ static uint16_t chunkIndex; 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; + autoSweepFreqStep = ( setting.ScanStop - setting.ScanStart ) / displayPoints; vbw = autoSweepFreqStep / 1000.0; // Set the video resolution ownrbw = setting.Bandwidth10 / 10.0; // and the resolution bandwidth @@ -97,6 +112,12 @@ static uint16_t chunkIndex; old_ownrbw = ownrbw; } +/* + * The FIR filters in the SI4432 are centred above the nominal IF frequency as that is where the signals are meant to be. + * To make sure we are not on the edge of the filters offset the IF frequency down by half the bandwidth + */ + offsetIF = setting.IF_Freq - setting.Bandwidth10 * 50; // bW10 is in kHz * 10, so * 100-> kHz, halved + /* * Need multiple readings for each pixel in the display to avoid missing signals. * Work out how many points needed for the whole sweep: @@ -104,8 +125,8 @@ static uint16_t chunkIndex; 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 + if ( sweepPoints < displayPoints ) + sweepPoints = displayPoints; // At least the right number of points for the display sweepFreqStep = ( setting.ScanStop - setting.ScanStart ) / sweepPoints; // Step for each reading @@ -120,14 +141,17 @@ static uint16_t chunkIndex; xmit.SetPowerReference ( setting.ReferenceOut ); // Set the GPIO reference output if wanted + maxGrid = setting.MaxGrid; + minGrid = setting.MinGrid; + #ifdef USE_WIFI // Vary number of points to send in each chunk depending on delaytime // A chunk is sent at the end of each sweep regardless wiFiPoints = wiFiTargetTime / delaytime; if (wiFiPoints > MAX_WIFI_POINTS) wiFiPoints = MAX_WIFI_POINTS; - if (wiFiPoints > DISPLAY_POINTS*OVERLAP) - wiFiPoints = DISPLAY_POINTS*OVERLAP; + if (wiFiPoints > displayPoints*OVERLAP) + wiFiPoints = displayPoints*OVERLAP; //Serial.printf("No of wifiPoints set to %i\n", wiFiPoints); if ( numberOfWebsocketClients > 0 ) @@ -169,11 +193,13 @@ static uint16_t chunkIndex; 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; + tempIF = offsetIF - IF_Shift; } else { - tempIF = setting.IF_Freq; + tempIF = offsetIF; } + + spurToggle = !spurToggle; //Serial.printf("tempIF %u, spurOffset=%i, spur:%i, Toggle:%i\n", tempIF, tempIF - setting.IF_Freq, setting.Spur, spurToggle); @@ -269,6 +295,7 @@ static uint16_t chunkIndex; pointsPastPeak = 0; // Avoid possible peak detection at start of sweep peakRSSI = 0; + maxRSSI = 0; sweepStartDone = true; // Make sure this initialize is only done once per sweep initSweep = false; @@ -347,7 +374,7 @@ static uint16_t chunkIndex; 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()); +// Serial.printf("LO Required: %i Actual %i\n", tempIF+autoSweepFreq, xmit.GetFrequency()); #ifdef SI_TG_LO_CS if (tgLO_OK && (trackGenSetting.Mode == 1) ) @@ -496,11 +523,11 @@ static uint16_t chunkIndex; if (( autoSweepFreq > MARKER_MIN_FREQUENCY ) && (oldSweepStep > 0)) { - if ( peakLevel < myData[oldSweepStep] ) + if ( maxRSSI <= myActual[oldSweepStep] ) { - peakIndex = oldSweepStep; - peakLevel = myData[oldSweepStep]; - peakFreq = oldSweepFreq; + maxIndex = oldSweepStep; + maxRSSI = myActual[oldSweepStep]; + maxFreq = oldSweepFreq; // Serial.printf( "peakLevel set %i, index %i\n", peakLevel, oldSweepStep); // displayPeakData (); @@ -586,15 +613,15 @@ static uint16_t chunkIndex; } // If in the last few points and gain trace is displayed show the gain scale - if ( setting.ShowGain && (oldSweepStep > DISPLAY_POINTS - 2 * CHAR_WIDTH) ) + if ( setting.ShowGain && (oldSweepStep > displayPoints - 2 * CHAR_WIDTH) ) { - int16_t scaleX = DISPLAY_POINTS - 2 * CHAR_WIDTH - oldSweepStep + 1; // relative to the img sprite + int16_t scaleX = displayPoints - 2 * CHAR_WIDTH - oldSweepStep + 1; // relative to the img sprite img.setPivot( scaleX, 0); gainScaleSprite.pushRotated ( &img, 0, TFT_BLACK ); // Send the sprite to the target sprite, with transparent colour } if ( oldSweepStep > 0 ) // Only push if not first point (two pixel wide img) - img.pushSprite ( X_ORIGIN+oldSweepStep-1, Y_ORIGIN ); + img.pushSprite ( xOrigin+oldSweepStep-1, yOrigin ); myFreq[oldSweepStep] = oldSweepFreq; // Store the frequency for XML file creation @@ -609,13 +636,15 @@ static uint16_t chunkIndex; if ( sweepCount < 2 ) sweepCount++; // Used to disable wifi at start - oldPeakLevel = peakLevel; //Save value of peak level for use by the "SetPowerLevel" function +// Serial.printf("MaxRSSI = %i, freq = %i\n", maxRSSI, maxFreq); - if ( myActual[DISPLAY_POINTS-1] == 0 ) // Ensure a value in last data point + oldPeakLevel = maxRSSI; //Save value of peak level for use by the "SetPowerLevel" function + + if ( myActual[displayPoints-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; + myActual[displayPoints-1] = rxRSSI; // Yes, save it + myGain[displayPoints-1] = gainReading; + myFreq[displayPoints-1] = oldSweepFreq; } if ( showRSSI == 1 ) // Only show it once? diff --git a/cmd.cpp b/cmd.cpp index 5491089..4f1a0f6 100644 --- a/cmd.cpp +++ b/cmd.cpp @@ -17,9 +17,9 @@ * here in place of much redundant code in those modules. */ -#include "Cmd.h" // Our associated header +#include "cmd.h" // Our associated header #include "preferences.h" // For "SAVE" & "RECALL" -#include "Marker.h" // Marker class definition +#include "marker.h" // Marker class definition /* * These are the objects for the PE4302 and Si4432 modules. They are created in the * main program file: @@ -47,11 +47,29 @@ 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]; +/* + * Variables to determine size of grid and waterfall + * In Bandscope mode the grid is reduced. In future it may be possible to add + * a waterfall to the main sweep, but not yet + */ +extern uint16_t gridHeight; +extern uint16_t gridWidth; +extern uint16_t yGrid; // no of grid divisions +extern uint16_t yDelta; // no of points/division +extern uint16_t xGrid; +extern uint16_t xOrigin; +extern uint16_t yOrigin; +extern uint16_t displayPoints; +extern uint16_t xDelta; +extern uint16_t waterfallHeight; + + +extern uint8_t myData[SCREEN_WIDTH+1]; +extern uint8_t myStorage[SCREEN_WIDTH+1]; extern float bandwidth; // The current bandwidth (not * 10) extern unsigned long delaytime; // In microseconds +extern unsigned long offsetDelayTime; // In microseconds extern unsigned long wiFiTargetTime; extern uint16_t wiFiPoints; @@ -76,10 +94,15 @@ 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 uint32_t startFreq_RX; // Last value of the IF sweep start frequency +extern uint32_t stopFreq_RX; // Last value of the IF sweep end frequency +extern uint32_t sigFreq_RX; // 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 uint32_t colourTest; extern Marker marker[MARKER_COUNT]; // Array of marker objects @@ -97,6 +120,9 @@ extern void initSweepLow (); extern void initSigLow (); extern void initIF_Sweep (); extern void setMode (uint16_t newMode); +extern uint8_t dBmToRSSI ( double dBm ); + + /* * The "msgTable" provides a way of translating the ASCII message to the internal * number. Needs to be re-ordered based on expected usage. @@ -143,14 +169,20 @@ extern void setMode (uint16_t newMode); { "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 + { "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 + { "IFSIGNAL", MSG_IFSIGNAL }, // IF sweep signal frequency + { "TGOFFSET", MSG_TGOFFSET }, // Offset TG IF from SA IF to reduce pass through + { "CTEST", MSG_COLOURTEST}, // Work out how colours work! + { "OFFDEL", MSG_OFFDELAY }, // Offset tuning delay + { "WFMIN", MSG_WFMIN }, // Min level for waterfall colours + { "WFGAIN", MSG_WFGAIN }, // Gain for waterfall colours + { "RXSWEEP", MSG_RX_SWEEP }, // Set RX Sweep Mode + { "RXSIGNAL", MSG_RXSIGNAL }, // IF sweep signal frequency + { "", MSG_NONE } // Unrecognized command }; int msgCount = ELEMENTS ( msgTable); @@ -197,6 +229,7 @@ void ShowMenu () 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 ( " RXSWEEP.......Set to RX Sweep mode to analyse the TinySA FIR filters" ); Serial.println ( " BANDSCOPE.....Set to BANDSCOPE mode" ); Serial.println ( " TRACES........Turn display traces on or off ['GAIN' or 'dB']" ); @@ -208,6 +241,9 @@ void ShowMenu () Serial.printf ( " SCALE.........Set the dB/horizontal line value; currently: %ddB\n", setting.PowerGrid ); + Serial.printf ( " WFMIN.........Set the minimum RSSI level for waterfall colouring: %i\n", setting.WaterfallMin ); + Serial.printf ( " WFGAIN........Set the gain for waterfall colouring: %d\n", setting.WaterfallGain ); + Serial.println ( "\nOther Commands:\n" ); Serial.print ( " DRIVE.........Local oscillator drive level [0 to 7]; currently: " ); @@ -240,6 +276,8 @@ void ShowMenu () Serial.printf ( " DELAY.........Timestep in uS, currently: %uuS\n", delaytime ); + Serial.printf ( " OFFDEL........Timestep in uS for Bandscope, currently: %uuS\n", offsetDelayTime ); + Serial.print ( " VFO...........Set or get active VFO [R, ( L or T ), I(tg If), G(tG LO)]; currently: " ); if ( VFO == RX_4432 ) @@ -442,6 +480,7 @@ int16_t addr; // Si4432 register address int16_t tempValue; // Temporary value of something int32_t tempFreq; // Temporary frequency +float tempFloat; uint32_t freq1; // Several other temporary frequencies uint32_t freq2; // used in computing the center @@ -641,7 +680,7 @@ uint8_t reg69; // Ditto * 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. + * If no number is entered, we report the current setting. */ case MSG_SCALE: @@ -926,6 +965,20 @@ uint8_t reg69; // Ditto Serial.printf ( "Time per scan step = %uuS\n", delaytime ); return true; +/* + * The "OFFDEL" command sets the timestep in uS for tuning using offsets. + */ + + case MSG_OFFDELAY: // Set the timestep in uS, currently 2000 + if ( dataLen != 0 ) // Anything entered? + { + offsetDelayTime = atol ( dataBuff ); // Yes, set new delay time + DisplayInfo (); + } + + Serial.printf ( "Offset delay time per bandscope step = %uuS\n", offsetDelayTime ); + return true; + /* * The "VFO" command sets the active VFO @@ -1270,6 +1323,9 @@ uint8_t reg69; // Ditto * 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. + * + * We need to force the SI4432 to recalibrate the PLL after each tune by putting it into READY mode + * */ case MSG_TUNE: @@ -1453,7 +1509,7 @@ uint8_t reg69; // Ditto { tempValue = atoi ( dataBuff ); // Yes, get new value // check valid - if ( (tempValue > 10) && (tempValue < DISPLAY_POINTS * OVERLAP) ) + if ( (tempValue > 10) && (tempValue < displayPoints * OVERLAP) ) { wiFiPoints = tempValue; Serial.printf ( "Chunk size set to: %u\n", tempValue ); @@ -1461,7 +1517,7 @@ uint8_t reg69; // Ditto } else { - Serial.printf("Invalid - must be between 10 and %u\n", DISPLAY_POINTS * OVERLAP); + Serial.printf("Invalid - must be between 10 and %u\n", displayPoints * OVERLAP); return false; } } @@ -1505,6 +1561,10 @@ uint8_t reg69; // Ditto setMode(IF_SWEEP); return true; + case MSG_RX_SWEEP: + setMode(RX_SWEEP); + return true; + case MSG_TGOFFSET: if ( dataLen != 0 ) // Frequency specified? { @@ -1526,7 +1586,7 @@ uint8_t reg69; // Ditto freq1 = ParseFrequency ( dataBuff ); // Yes, get new frequency SetIFsweepSigFreq (freq1); } - Serial.printf ( "If Signal frequency: %s\n", FormatFrequency ( GetIFsweepSigFreq() )); + Serial.printf ( "IF Sweep Signal frequency: %s\n", FormatFrequency ( GetIFsweepSigFreq() )); return true; @@ -1557,6 +1617,70 @@ uint8_t reg69; // Ditto return false; } + + case MSG_COLOURTEST: + if ( dataLen != 0 ) // Anything entered? + { + tempValue = atoi ( dataBuff ); // Yes, get new value + if ( ( tempValue >= 0 ) && (tempValue <= 65535) ) + { + colourTest = tempValue; + return true; + } + else // Nothing specified + { + Serial.println ( "Invalid Colour" ); + return false; + } + } + +/* + * The "WFMIN" command sets the min RSSI level for waterfall colouring. + * The value is entered in dBm so the value can be judged from the waterfall + * + * If no number is entered, we report the current setting. + */ + case MSG_WFMIN: + if ( dataLen != 0 ) // Value specified? + { + tempValue = atoi ( dataBuff ); // Yes, get grid reference value + SetWFMin ( tempValue ); // Set it + DisplayInfo (); // Re display the scan + } + + Serial.printf ( "Waterfall Minimum RSSI value: %i\n", setting.WaterfallMin ); + return true; + +/* + * The "WFGAIN" command sets the min RSSI level for waterfall colouring. + * + * If no number is entered, we report the current setting. + */ + case MSG_WFGAIN: + if ( dataLen != 0 ) // Value specified? + { + tempFloat = atof ( dataBuff ); // Yes, get grid reference value + SetWFGain ( tempFloat ); // Set it + Serial.printf("tempFloat = %4.2f\n", tempFloat); + DisplayInfo (); // Re display the scan + } + + Serial.printf ( "Waterfall colour gain: %4.2f\n", setting.WaterfallGain ); + return true; + +/* + * Set signal frequency fro RX Sweep + */ + case MSG_RXSIGNAL: + if ( dataLen != 0 ) // Frequency specified? + { + freq1 = ParseFrequency ( dataBuff ); // Yes, get new frequency + SetRXsweepSigFreq (freq1); + } + Serial.printf ( "RX Sweep Signal frequency: %s\n", FormatFrequency ( GetRXsweepSigFreq() )); + return true; + + default: return false; } // End of "switch" @@ -1953,7 +2077,7 @@ int oldMin = setting.MinGrid; int oldMax = setting.MaxGrid; setting.MaxGrid = ref; - setting.MinGrid = ref - Y_GRID * setting.PowerGrid; + setting.MinGrid = ref - yGrid * setting.PowerGrid; if (( oldMin != setting.MinGrid ) || ( oldMax != setting.MaxGrid )) { @@ -2014,7 +2138,7 @@ void SetPowerGrid ( int g ) int oldGrid = setting.PowerGrid; setting.PowerGrid = g; - setting.MinGrid = setting.MaxGrid - Y_GRID * g; + setting.MinGrid = setting.MaxGrid - yGrid * g; if ( oldGrid != setting.PowerGrid ) { @@ -2083,7 +2207,7 @@ int oldAtten = setting.Attenuate; void SetStorage ( void ) { - for ( int i = 0; i < DISPLAY_POINTS; i++ ) + for ( int i = 0; i < displayPoints; i++ ) myStorage[i] = myData[i]; setting.ShowStorage = true; @@ -2179,6 +2303,7 @@ int16_t tempBw; if ( setting.Bandwidth10 != oldRBW ) { changedSetting = true; + SetRXsweepSpan (setting.Bandwidth10 * 1000); // 10 * bandwidth WriteSettings (); } } @@ -2622,6 +2747,189 @@ uint32_t GetIFsweepSigFreq ( void ) } +/* + * Specific span setting for RX Sweep mode + * + * + * "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 SetRXsweepSpan ( 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 = startFreq_RX; // Save the current start frequency + lastStop = stopFreq_RX; // and the current stop frequency + + halfSpan = spanRange / 2; // Half the requested span + centerFreq = setting.IF_Freq; // Always the IF Freq for this mode + + startFreq = centerFreq - halfSpan; // New computed start frequency + stopFreq = centerFreq + 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 > RX_STOP_MAX ) // Range check the stop frequency + { + stopFreq = RX_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 < RX_START_MIN ) // Range check the start frequency + { + startFreq = RX_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 )); + + startFreq_RX = startFreq; // Set new start frequency + stopFreq_RX = stopFreq; // Set new stop frequency + + if (( startFreq_RX != lastStart ) + || ( stopFreq_RX != lastStop )) // Did it change + { + changedSetting = true; // Yes it did + RedrawHisto (); // Redraw labels and restart sweep with new settings + } +} + + +uint32_t GetRXsweepSpan () +{ + return stopFreq_RX - startFreq_RX; +} + + +void SetRXsweepStart ( 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_RX; // Save the current start frequency + lastStop = stopFreq_RX; // and the current stop frequency + + if ( startFreq < RX_START_MIN ) // Range check + startFreq = RX_START_MIN; + + if ( startFreq > RX_STOP_MAX ) // Range check + startFreq = RX_STOP_MAX; + + if ( startFreq > stopFreq_RX ) // Start can't be more than current stop + stopFreq_RX = RX_STOP_MAX; // So set stop at maximum + + startFreq_RX = startFreq; // Set new start frequency + + if (( startFreq_RX != lastStart ) + || ( stopFreq_RX != 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 GetRXsweepStart ( void ) +{ + return startFreq_RX; +} + + + + +void SetRXsweepStop ( 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_RX; // Save the current start frequency + lastStop = stopFreq_RX; // and the current stop frequency + + if ( stopFreq < RX_START_MIN ) // Range check + stopFreq = RX_START_MIN; + + if ( stopFreq > RX_STOP_MAX ) // Range check + stopFreq = RX_STOP_MAX; + + if ( stopFreq < startFreq_RX ) // Stop can't be less than current start + startFreq_RX = RX_START_MIN; // so set start to minimum frequency + + stopFreq_RX = stopFreq; // Set new stop frequency + + if (( startFreq_RX != lastStart ) + || ( stopFreq_RX != 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 GetRXsweepStop ( void ) +{ + return stopFreq_RX; +} + + +void SetRXsweepSigFreq ( uint32_t sigFreq ) +{ +uint32_t lastSigFreq; + +// Serial.print ( "Requested RX sweep signal = " ); Serial.println ( FormatFrequency ( sigFreq_RX )); + + lastSigFreq = sigFreq_RX; + +// 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_RX = sigFreq; + if ( sigFreq_RX != lastSigFreq ) + changedSetting = true; + } + + RedrawHisto (); // Redraw labels and restart sweep with new settings + +} + + +uint32_t GetRXsweepSigFreq ( void ) +{ + return sigFreq_RX; +} + + + /* * Specific start/stop frequency setting for Bandscope mode */ @@ -2646,8 +2954,29 @@ uint32_t lastStart; // Old sweep start frequency { changedSetting = true; // Yes it did initSweep = true; -// RedrawHisto (); // Redraw labels and restart sweep with new settings -// WriteSettings (); // and save the setting structure + WriteSettings (); // and save the setting structure + } +} + + +/* + * "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 SetBandscopeRBW ( int bw ) +{ +int16_t oldRBW = setting.BandscopeRBW10; +int16_t tempBw = bw; + + setting.BandscopeRBW10 = bw; + + tempBw = rcvr.SetRBW ( bw, &delaytime ); + + if ( tempBw != oldRBW ) + { + changedSetting = true; + WriteSettings (); } } @@ -2662,7 +2991,6 @@ uint32_t GetBandscopeStart ( void ) /* * Specific start/stop frequency setting for Bandscope mode */ - void SetBandscopeSpan ( uint32_t spanFreq ) { uint32_t lastSpan; // Old sweep span @@ -2695,6 +3023,25 @@ uint32_t GetBandscopeSpan ( void ) } +/* + * "SetBandscopeLevel" - Sets the decibel level for the top line of the graph in Bandscope mode. + */ + +void SetBandscopeLevel ( int ref ) +{ +int oldMin = setting.BandscopeMinGrid; +int oldMax = setting.BandscopeMaxGrid; + + setting.BandscopeMaxGrid = ref; + setting.BandscopeMinGrid = ref - yGrid * setting.PowerGrid; + + if (( oldMin != setting.BandscopeMinGrid ) || ( oldMax != setting.BandscopeMaxGrid )) + { + changedSetting = true; + WriteSettings (); + } +} + /* @@ -2884,3 +3231,18 @@ void SetFreq ( int vfo, uint32_t freq ) if ( vfo == TX_4432 ) // Transmitter? xmit.SetFrequency ( freq ); } + +void SetWFMin (int16_t level) +{ + if ( (level >=-130) && (level < -50) ) + setting.WaterfallMin = dBmToRSSI( (double)level ); + Serial.printf("Watterfall min set to %i \n", setting.WaterfallMin ); + WriteSettings(); +} + +void SetWFGain (float gain) +{ + if ((gain > 0.1) && (gain < 3.0)) + setting.WaterfallGain = gain; + WriteSettings(); +} diff --git a/cmd.h b/cmd.h index 70f51f3..1b90a97 100644 --- a/cmd.h +++ b/cmd.h @@ -13,8 +13,8 @@ #define _CMD_H_ // Prevent double inclusion #include "simpleSA.h" // General program definitions -#include "PE4302.h" // Class definition for thePE4302 attenuator -#include "Si4432.h" // Class definition for the Si4432 transceiver +#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 @@ -72,6 +72,13 @@ #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 +#define MSG_OFFDELAY 46 // Adjust offset tuning delay time before taking reading +#define MSG_WFMIN 47 // set the min level for the waterfall colours +#define MSG_WFGAIN 48 // set the gain for waterfall colouring +#define MSG_RX_SWEEP 49 // Set the RX Sweep Mode +#define MSG_RXSIGNAL 50 // Set frequency of injected signal for IF Sweep + +#define MSG_COLOURTEST 99 // test of waterfall colours - remove this when done! typedef struct // Keeps everything together @@ -161,24 +168,40 @@ 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 +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 +void SetBandscopeSpan ( uint32_t freq ); // Added in Version 3.0f uint32_t GetBandscopeSpan ( void ); -void SetIFsweepStop ( uint32_t freq ); // Added in Version 3.0c +void SetBandscopeRBW ( int v ); // Sets the resolution bandwidth + +void SetBandscopeLevel ( int ref ); // Sets the decibel level for the top line of the graph + +void SetIFsweepStart ( uint32_t freq ); // Added in Version 3.0c +uint32_t GetIFsweepStart ( 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 SetRXsweepStart ( uint32_t freq ); +uint32_t GetRXsweepStart ( void ); + +void SetRXsweepStop ( uint32_t freq ); +uint32_t GetRXsweepStop ( void ); + +void SetRXsweepSpan ( uint32_t freq ); +uint32_t GetRXsweepSpan ( void ); + +void SetRXsweepSigFreq ( uint32_t freq ); +uint32_t GetRXsweepSigFreq ( void ); + void SetSweepCenter ( uint32_t freq, uint8_t span ); uint32_t GetSweepCenter ( void ); @@ -189,4 +212,8 @@ void SetFreq ( int vfo, uint32_t freq ); bool UpdateMarker ( uint8_t mkr, char action ); +void SetWFMin (int16_t level); +void SetWFGain (float gain); + + #endif // End of "Cmd.h" diff --git a/marker.cpp b/marker.cpp index db6430a..7a6d533 100644 --- a/marker.cpp +++ b/marker.cpp @@ -2,7 +2,7 @@ * "Marker.cpp" Contains the code for the "Marker" class functions */ -#include "Marker.h" // Class definition +#include "marker.h" // Class definition /* * There are two constructors; the first simply creates an uninitialized diff --git a/menu.cpp b/menu.cpp index 8c8d960..fe9a146 100644 --- a/menu.cpp +++ b/menu.cpp @@ -2,7 +2,7 @@ * "Menu.cpp" implements the "Menuitem" class. */ -#include "Menu.h" // Class definition +#include "menu.h" // Class definition Menuitem::Menuitem () {} // Placeholder constructor diff --git a/my_SA.h b/my_SA.h index 3b0fe44..2161645 100644 --- a/my_SA.h +++ b/my_SA.h @@ -92,6 +92,18 @@ #define IF_STOP_MAX 500000000 // 500MHz #define IF_START_MIN 400000000 // 400MHz + +/* + * Some definitions for RX-Sweep to test the internal FIR filters + */ + + #define RX_SWEEP_START 432000000 + #define RX_SWEEP_STOP 436000000 + +// Limits for keypad entry of IF sweep frequency start/stop + #define RX_STOP_MAX 440000000 // 500MHz + #define RX_START_MIN 430000000 // 400MHz + /* * Spur reduction shifts the IF down from its normal setting every other scan @@ -200,8 +212,8 @@ * 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 TX_CAPACITANCE 0x65 // Transmitter (LO) crystal load capacitance + #define RX_CAPACITANCE 0x59 // 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 diff --git a/pE4302.cpp b/pE4302.cpp index 522256e..734546f 100644 --- a/pE4302.cpp +++ b/pE4302.cpp @@ -8,7 +8,7 @@ * I2C GPIO expander to drive a parallel version of the PE4302 module. */ -#include "PE4302.h" +#include "pE4302.h" /* diff --git a/si4432.cpp b/si4432.cpp index 966adde..461cd29 100644 --- a/si4432.cpp +++ b/si4432.cpp @@ -14,7 +14,7 @@ */ #include // Serial Peripheral Interface library -#include "Si4432.h" // Our header file +#include "si4432.h" // Our header file #include /* @@ -25,21 +25,35 @@ 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 + { 26, 6800, 0, 5, 1 }, // 0 "AUTO" selection possibility + { 28, 6600, 0, 5, 2 }, // 1 "AUTO" selection possibility + { 31, 6600, 0, 5, 3 }, // 2 If user selects 3KHz -> 3.1KHz actual + { 32, 6600, 0, 5, 4 }, // 3 "AUTO" selection possibility + { 37, 6600, 0, 5, 5 }, // 4 "AUTO" selection possibility + { 42, 6600, 0, 5, 6 }, // 5 "AUTO" selection possibility + { 45, 5500, 0, 5, 7 }, // 6 "AUTO" selection possibility + { 49, 5000, 0, 4, 1 }, // 7 "AUTO" selection possibility + { 54, 4200, 0, 4, 2 }, // 8 "AUTO" selection possibility + { 59, 3700, 0, 4, 3 }, // 9 "AUTO" selection possibility + { 72, 3300, 0, 4, 5 }, // 10 "AUTO" selection possibility + { 106, 2500, 0, 3, 2 }, // 11 If user selects 10KHz -> 10.6KHz actual + { 162, 2000, 0, 3, 6 }, // 12 "AUTO" selection possibility + { 210, 1600, 0, 2, 2 }, // 13 "AUTO" selection possibility + { 227, 1500, 0, 2, 3 }, // 14 "AUTO" selection possibility + { 240, 1400, 0, 2, 4 }, // 15 "AUTO" selection possibility + { 282, 1000, 0, 2, 5 }, // 16 "AUTO" selection possibility + { 322, 1000, 0, 2, 6 }, // 17 If user selects 30KHz -> 32.2KHz actual + { 377, 1000, 0, 1, 1 }, // 18 "AUTO" selection possibility + { 562, 700, 0, 1, 5 }, // 19 "AUTO" selection possibility + { 832, 600, 0, 0, 2 }, // 20 "AUTO" selection possibility + { 1121, 500, 0, 0, 5 }, // 21 If user selects 100KHz -> 112.1KHz actual + { 1811, 500, 1, 1, 9 }, // 22 "AUTO" selection possibility + { 2251, 1400, 1, 0, 1 }, // 23 "AUTO" selection possibility + { 2488, 450, 1, 0, 2 }, // 24 "AUTO" selection possibility + { 3355, 400, 1, 0, 8 }, // 25 If user selects 300KHz -> 335.5KHz actual + { 3618, 300, 1, 0, 9 }, // 26 "AUTO" selection possibility + { 4685, 300, 1, 0, 11 }, // 27 "AUTO" selection possibility + { 6207, 300, 1, 0, 14 } // 28 "AUTO" selection possibility }; @@ -97,82 +111,6 @@ 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 () -{ - /* @@ -257,10 +195,32 @@ void Si4432::SubInit () WriteByte ( REG_GPIO1, 0x15 ); // GPIO-1 RX State (output) WriteByte ( REG_GPIO2, 0x1F ); // Set GPIO-2 output to ground until needed - + + +/* + * + * 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 + */ + + Tune ( cap ); // Set the crystal capacitance to fine tune frequency + + WriteByte ( REG_OFC1, ( RXON | PLLON | XTON )); // Receiver, PLL and "Ready Mode" all on + + 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; } + /* * "Reset" - Initializes the Si4432. * @@ -369,7 +329,7 @@ uint8_t Si4432::ReadByte ( uint8_t reg ) void Si4432::SetFrequency ( uint32_t freq ) { -int hbsel; // High/low band select bit +int hbsel; // high band select, but shifted to the correct location in the byte int sbsel; // Sideband select bit uint16_t carrier; // Carrier frequency uint32_t reqFreq = freq; // Copy of requested frequency @@ -392,7 +352,7 @@ uint8_t registerBuf[4]; // Used to send frequency data in burst mode /* * 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 + * Frequency resoluion is 156.25Hz in low band, 312.5Hz in high band but freq is already divided above */ freq = freq + 78; /* @@ -440,56 +400,31 @@ uint8_t registerBuf[4]; // Used to send frequency data in burst mode /* * M0WID mod - Update the frequency in "burst" mode as opposed to separate - * writes for each register, but only update what is needed + * writes for each register */ 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 -// -// } + 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; uint32_t fb = ( fbs & F_BAND ) ; - hbsel = hbsel>>5; // should be 1 or 0 + _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) ; + _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 ); @@ -518,7 +453,87 @@ uint8_t registerBuf[4]; // Used to send frequency data in burst mode * handled by the calling program. */ -// delayMicroseconds ( _dt ); // M0WID - Delay depends on RBW +// delayMicroseconds ( _dt ); +} + + + +/* + * "SetOffsetFreq" adjusts the frequency from the nominal value set in SetFrequency. + * It is intended for use by the SI4432 AFC algorithm to enable more accurate + * frequency matching of different radios, but we can turn off the AFC and use it + * to adjust the frequency without having the Si4432 go through its TX-RX-TX state machine + * and enable reduced edlay times from setting a frequency to getting valid readings + * The offset can vary +- 80kHz in low bands (f<480MHz) and +-160kHz in high bands (480-960MHz) + * negative numbers are twos complement of the positive offset + */ +void Si4432::SetOffsetFreq ( int32_t freq ) +{ + uint8_t registerBuf[3]; // Used to send data in burst mode + + uint32_t posFreq; + if (freq < 0) + posFreq = -freq; + else + posFreq = freq; + + uint16_t fo = (double)posFreq/(156.25 * (double)(_hbsel + 1)); + + if (freq < 0) + // do twos complement - invert the bits (0-9) (use XOR) and add one + fo = (fo ^ 0x3FF) + 1; + + //Serial.printf(" offset frequency %i fo=%3x \n", freq, fo); + + // write to the Si4432 + registerBuf[0] = REG_FOFF1|0x80; // First register in write mode (bit 7 set) + registerBuf[1] = fo & 0xFF; // first 8 bits + registerBuf[2] = (fo >> 8 ) & 0x03; // last 2 bits + + //_spi->beginTransaction ( SPISettings ( BUS_SPEED, BUS_ORDER, BUS_MODE )); + // spiSimpleTransaction(_spi->bus()); + digitalWrite ( _cs, LOW ); // Select the correct device + _spi->transfer ( registerBuf, 3 ); // Send the data + digitalWrite ( _cs, HIGH ); // Deselect the device + //_spi->endTransaction(); + +} + + + +/* + * "SetOffset" sets the frequency offset registers to correspond to the number of offset steps + */ +void Si4432::SetOffset ( int32_t offset ) +{ + uint8_t registerBuf[3]; // Used to send data in burst mode + + uint16_t posOffset; + if (offset < 0) + posOffset = -offset; + else + posOffset = offset; + + uint16_t fo = posOffset; + + if (offset < 0) + // do twos complement - invert the bits (0-9) (use XOR) and add one + fo = (fo ^ 0x3FF) + 1; + + //Serial.printf(" posoffset %3x, offset %i fo=%3x \n", posOffset, offset, fo); + + // write to the Si4432 + registerBuf[0] = REG_FOFF1|0x80; // First register in write mode (bit 7 set) + registerBuf[1] = fo & 0xFF; // first 8 bits + registerBuf[2] = (fo >> 8 ) & 0x03; // last 2 bits + + //_spi->beginTransaction ( SPISettings ( BUS_SPEED, BUS_ORDER, BUS_MODE )); + // spiSimpleTransaction(_spi->bus()); + digitalWrite ( _cs, LOW ); // Select the correct device + _spi->transfer ( registerBuf, 3 ); // Send the data + digitalWrite ( _cs, HIGH ); // Deselect the device + //_spi->endTransaction(); + } @@ -541,8 +556,11 @@ float Si4432::SetRBW ( float reqBandwidth10, unsigned long* delaytime_p ) // "re */ if ( reqBandwidth10 <= _bandpassFilters[0].bandwidth10 ) + { filter = 0; - + Serial.print ("Minimum RBW"); + } + /* * 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. @@ -551,7 +569,7 @@ float Si4432::SetRBW ( float reqBandwidth10, unsigned long* delaytime_p ) // "re while (( _bandpassFilters[filter-1].bandwidth10 > reqBandwidth10 - 0.01 ) && ( filter > 0 )) filter--; -// Serial.print ( "filter = " ); Serial.println ( filter ); + Serial.printf ( "Required = %f, filter = %i\n", reqBandwidth10, filter); Serial.println ( filter ); /* @@ -748,15 +766,35 @@ void Si4432::RxMode () // Put module in RX mode WriteByte ( REG_OFC1, ( RXON | PLLON | XTON )); // Receiver, PLL and "Ready Mode" all on } +void Si4432::ReadyMode () // Put module into ready mode, rx and tx off +{ + WriteByte ( REG_OFC1, XTON ); // "Ready Mode" on +} + +void Si4432::SetMode (uint8_t newMode) +{ + WriteByte ( REG_OFC1, newMode ); // "Ready Mode" on +} + +uint8_t Si4432::GetMode () +{ + return ReadByte ( REG_OFC1 ); +} /* * "Tune" sets the crystal tuning capacitance. + * + * We need to cycle the PLL to force a recalibration after the tune */ 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 + uint8_t currentMode = ReadByte ( REG_OFC1 ); + ReadyMode (); + delayMicroseconds(300); + SetMode ( currentMode ); } /* diff --git a/si4432.h b/si4432.h index ade99ba..07a34c5 100644 --- a/si4432.h +++ b/si4432.h @@ -142,6 +142,9 @@ #define REG_TXPWR 0x6D // Transmitter Power +#define REG_FOFF1 0x73 // Frequency Offset registers +#define REG_FOFF2 0x74 + #define REG_FBS 0x75 // Frequency Band Select #define SBSEL 0x40 #define HBSEL 0x20 @@ -175,9 +178,11 @@ 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 SetOffsetFreq ( int32_t freq ); // Set a frequency offset from the nominal frequency +void SetOffset ( int32_t offset ); // Offset based on number of offset increments + 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 @@ -188,7 +193,13 @@ bool PreAmpAGC(); // Reads the register and return true if agc set to a bool GetPreAmpAGC (); // Return true if agc set to auto uint8_t GetRSSI (); // Get Receiver Signal Strength + void RxMode (); // Put module in RX mode +void TxMode ( uint8_t level ); // Put module in TX mode +void ReadyMode (); // Put module in READY mode, crystal on, PLL off +uint8_t GetMode (); +void SetMode (uint8_t newMode); + 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 @@ -221,6 +232,7 @@ 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 +int _hbsel; // High band (1) or low band (0) 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 diff --git a/simpleSA.h b/simpleSA.h index 1b382cf..9dc31e0 100644 --- a/simpleSA.h +++ b/simpleSA.h @@ -26,13 +26,13 @@ #ifndef TINYSA_H_ #define TINYSA_H_ // Prevent double inclusion -#include "My_SA.h" // User settable parameters +#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 +#define PROGRAM_NAME "simpleSA" // These are for the WiFi interface +#define PROGRAM_VERSION "0.01" // Current version is 0.01 - beta! /* @@ -41,7 +41,7 @@ * it out, it will stay set at '1'. */ - #define TRACE_COUNT 1 // Number fo different traces available + #define TRACE_COUNT 1 // Number of different traces available /* @@ -49,6 +49,7 @@ */ #define DISPLAY_POINTS 290 // Number of scan points in a sweep + #define BANDSCOPE_POINTS 80 // Number of scan points in a bandscope 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 @@ -63,6 +64,9 @@ #define GRID_HEIGHT ( Y_GRID * DELTA_Y ) // Height of checkerboard + #define WATERFALL_HEIGHT 100 // Height of waterfall in Bandscope mode + + /* * Definitions used in signal generator mode */ @@ -175,7 +179,7 @@ */ 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 }; + IF_SWEEP, ZERO_SPAN_LOW_RANGE, ZERO_SPAN_HIGH_RANGE, TRACKING_GENERATOR, BANDSCOPE, RX_SWEEP }; /* @@ -229,7 +233,7 @@ typedef struct { int8_t Attenuate = 0; // Attenuator setting (***) int8_t Generate = 0; // Signal generator mode if not zero (***) int16_t Bandwidth10 = 0; // Resolution Bandwidth setting*10; 0 = auto - int16_t LevelOffset = 0; // Related to power reading; not sure what though! + int16_t LevelOffset = 0; // Calibration value (move to config?) int8_t ReferenceOut = 1; // Transmitter GPIO2 set to 15MHz int16_t PowerGrid = 10; // dB/vertical divison on the grid bool Spur = 0; // Spur reduction on or off @@ -239,7 +243,7 @@ typedef struct { bool SubtractStorage = 0; // Subtract stored scan (on or off) bool ShowGain = 1; // Display gain trace (on or off) bool ShowSweep = 1; // Display dB trace (on or off) - uint8_t Drive = 6; // LO Drive power (***) + uint8_t Drive = 6; // LO Drive power (***) (move to config?) uint8_t SigGenDrive = 5; // Signal generator drive (for RX SI4432) uint8_t spareUint8t_1 = 5; // spare uint8_t spareUint8t_2 = 5; // spare @@ -247,10 +251,14 @@ typedef struct { uint16_t Mode = SA_LOW_RANGE; // Default to low freq range Spectrum analyser mode uint16_t Timebase = 100; // Timebase for Zero IF modes (milliSeconds) int16_t TriggerLevel = -40; // Trigger level for ZeroIF mode (dBm) - uint32_t BandscopeStart = 7000000; // Start frequency for bandscope sweep + uint32_t BandscopeStart = 14000000; // Start frequency for bandscope sweep uint32_t BandscopeSpan = 200000; // Span for the bandscope sweep - uint16_t BandscopePoints = 80; // No of points in the sweep. 80/160/320 - + uint16_t BandscopePoints = BANDSCOPE_POINTS; // No of points in the sweep. 80/160/320 + int16_t BandscopeMaxGrid= -30; + int16_t BandscopeMinGrid= -130; + int16_t BandscopeRBW10 = 28; // Resolution Bandwidth setting*10; + int16_t WaterfallMin = 17; // RSSI values below this are treated as zero + double WaterfallGain = 1.5; // Multiply RSSI value to get colour intensity /* * The following line should read: diff --git a/simpleSA.ino b/simpleSA.ino index 4e163d7..349cf42 100644 --- a/simpleSA.ino +++ b/simpleSA.ino @@ -2,6 +2,7 @@ * "simpleSA.ino" * * This is the main program file for the "TinySA for ESP32" (spectrum analyzer) + * * * Copyright (C) 2020 David Wilde (M0WID), John Price (WA2FZW) * @@ -22,17 +23,21 @@ * 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. + * For more information on that version visit https://groups.io/g/HBTE/topics + * + * Glen VK3PE has designed a set of PCB - see http://www.carnut.info/tinySA/tinySA.html + * + * Erik has since gone on to produce a commecial version, google tinySA, and this software + * has been renamed simpleSA to avoid confusion. * * 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. * @@ -54,12 +59,10 @@ * * * 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. * @@ -69,18 +72,15 @@ * * * 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 @@ -88,14 +88,12 @@ * * * 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 @@ -128,10 +126,12 @@ * SPI now runs at 16MHz (was at 1MHz - oops!) * Additional functions in ui.cmd to allow signed frequency entry. * - * Moved to github for trial - * Added initial changes for bandscope mode - * Added indication of tracking generator on/off - * Renamed to simpleSA + * Version 3.0f Changes by M0WID + * renamed simpleSA. Doesn't seem that simple to me! + * First go at bandscope mode + * VSPI reduced to 10Mhz as some boards will not run at 16MHz + * Put onto github - lets see how it works! + * */ @@ -139,10 +139,10 @@ #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 +#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 @@ -163,7 +163,7 @@ /* * Note the actual definitions for display and touch screen pins used are defined - * in the file "M0WID_Setup_ILI9341_simpleSA.h" in the "User_Setups" directory of + * 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 @@ -356,22 +356,53 @@ size_t capacity = JSON_ARRAY_SIZE ( MAX_WIFI_POINTS + 1 ) 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 +/* + * Variables to determine size of grid and waterfall + * In Bandscope mode the grid is reduced. In future it may be possible to add + * a waterfall to the main sweep, but not yet + */ +uint16_t gridHeight = GRID_HEIGHT; +uint16_t gridWidth = DISPLAY_POINTS; +uint16_t yGrid = Y_GRID; // no of grid divisions +uint16_t yDelta = gridHeight / yGrid; // no of points/division +uint16_t xGrid = X_GRID; +uint16_t xOrigin = X_ORIGIN; +uint16_t yOrigin = Y_ORIGIN; +uint16_t displayPoints = DISPLAY_POINTS; +uint16_t xDelta = displayPoints / xGrid; +uint16_t waterfallHeight = WATERFALL_HEIGHT; +int16_t maxGrid; +int16_t minGrid; +/* + * Some varibales for the various operating modes + */ uint16_t tinySA_mode = SA_LOW_RANGE; // Low frequency range -const char *modeText[] = { "SALo", "SAHi", "SGLo", "SGHi", "IFSw", "0SpL", "0SpH", "TrGn" }; // For mode display +const char *modeText[] = { "SALo", "SAHi", "SGLo", "SGHi", "IFSw", "0SpL", "0SpH", "TrGn", "BScp", "RXSw" }; // For mode display + float bandwidth; // The current bandwidth (not * 10) -unsigned long delaytime = 2000; // M0WID - changed to microseconds and moved here +unsigned long delaytime = 2000; // delay time to allow SI4432 filters to settle -uint32_t steps = DISPLAY_POINTS; // Number of frequency steps in the sweep +uint32_t steps = displayPoints; // 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 +/* + * Variables for offset frequency tuning + */ +int32_t offsetFreq; // Frequency offset from nominal setting +int16_t offsetStep; // increments by one at each reading +int16_t offsetValue; // Offset value to be written to Si4432 +int16_t offsetIncrement; // Increment of offsetValue per reading +int32_t offsetFreqIncrement; // Increment offsetFreq per reading +unsigned long offsetDelayTime = 5000; // Delay time when using frequency offset to change frequency + + 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) @@ -389,6 +420,7 @@ 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 ResetRXsweepMenuStack ( void ); // Reinitialise stack for IF Sweep mode extern void ResetBandscopeMenuStack ( void ); // Reinitialise stack for Bandscope mode extern void ShowSplash ( void ); // Displays the splash screen @@ -403,8 +435,14 @@ extern void pushBandscopeSettings (); */ uint32_t startFreq_IF = IF_SWEEP_START; uint32_t stopFreq_IF = IF_SWEEP_STOP; -uint32_t sigFreq_IF = 20000000; // 30 Mhz reference +uint32_t sigFreq_IF = 15000000; // 15 Mhz reference +/* + * Variables for RXSweep Mode + */ +uint32_t startFreq_RX = RX_SWEEP_START; +uint32_t stopFreq_RX = RX_SWEEP_STOP; +uint32_t sigFreq_RX = 15000000; // 15 Mhz reference /* * Variables for timing websocket event checks: @@ -430,33 +468,33 @@ uint32_t sweepCount; // Used to inhibit handling Wifi until /* * These arrays contain the data for the various scan points; they are used in - * here and in the "simpleSA_wifi" modules: + * 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 +uint8_t myData[SCREEN_WIDTH+1]; +uint8_t myStorage[SCREEN_WIDTH+1]; +uint8_t myActual[SCREEN_WIDTH+1]; +uint8_t myGain[SCREEN_WIDTH+1]; // Preamp gain +uint32_t myFreq[SCREEN_WIDTH+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; +static int old_settingAttenuate = -1000; +static int old_settingPowerGrid = -1000; +static int old_settingMax = -1; +static int old_settingMin = -1; +static double old_startFreq = -1; +static double 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; @@ -721,7 +759,7 @@ bool fsStatus = false; // True if SPIFFS loads ok startAP (); // Start it up #else // Connect to the router using SSID - // and password defined in simpleSA.h + // and password defined in TinySA.h Serial.println ( "Connecting..." ); // Indicate trying to connect @@ -769,7 +807,7 @@ bool fsStatus = false; // True if SPIFFS loads ok // tft.println("Setup Complete"); - Serial.printf ( "\nsimpleSA Version %s Initialization Complete\n", PROGRAM_VERSION ); + Serial.printf ( "\nTinySA Version %s Initialization Complete\n", PROGRAM_VERSION ); ShowMenu (); // Display the menu of commands on serial monitor @@ -860,6 +898,10 @@ loopStartMicros = micros(); doIF_Sweep(); // IF Sweep break; + case RX_SWEEP: + doRX_Sweep(); // RX Sweep + break; + case BANDSCOPE: doBandscope(); // Bandscope Sweep break; @@ -899,14 +941,19 @@ void setMode ( uint16_t newMode ) // initSigHigh(); // break; + case BANDSCOPE: + initBandscope(); + break; + case IF_SWEEP: initIF_Sweep(); break; - case BANDSCOPE: - initBandscope(); + case RX_SWEEP: + initRX_Sweep(); break; + default: DisplayError ( ERR_WARN, "Invalid Mode!", @@ -952,6 +999,13 @@ void menuExit() initIF_Sweep(); break; + case RX_SWEEP: + if ( setting.Mode == RX_SWEEP ) + RedrawHisto(); + else + initRX_Sweep(); + break; + case BANDSCOPE: if ( setting.Mode == BANDSCOPE ) RedrawHisto(); @@ -968,7 +1022,7 @@ void menuExit() /* - * Initialise common to low, high and IF_Sweeps + * Initialise common to low, high, RX and IF_Sweeps */ void init_sweep() { @@ -990,7 +1044,7 @@ void init_sweep() 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 + img.createSprite ( 2, gridHeight + 1 ); // Only 2 columns wide /* @@ -1033,21 +1087,22 @@ void init_sweep() old_settingSpur = -100; old_bandwidth = 0; - SetRX ( 0 ); // LO to transmit, RX to receive + SetRX ( 0 ); // LO to transmit, RX to receive + xmit.SetOffset ( 0 ); // make sure frequency offset registers are zero 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 + 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 + tg_lo.TxMode ( trackGenSetting.LO_Drive ); // turn on the Local Oscillator in tracking generator } #endif @@ -1331,7 +1386,17 @@ void RedrawHisto () old_settingAverage = -1; old_settingSpur = -100; ClearDisplay(); - DisplayInfo(); + switch (tinySA_mode) + { + case BANDSCOPE: + DisplayBandscopeInfo(); + break; + + default: + DisplayInfo(); + break; + + } DrawFullCheckerBoard(); } @@ -1350,7 +1415,7 @@ 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 }; +// 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, RX_SWEEP }; tSprite.fillSprite ( BLACK ); tSprite.setTextColor ( WHITE ); @@ -1376,10 +1441,10 @@ double fStop; sSprite.setCursor ( 0, CHAR_HEIGHT * 2 ); sSprite.setTextColor ( DB_COLOR ); - sSprite.printf ( "%4i", setting.MaxGrid ); + sSprite.printf ( "%4i", maxGrid ); - sSprite.setCursor ( 0, GRID_HEIGHT + Y_ORIGIN ); - sSprite.printf ( "%4i", setting.MinGrid ); + sSprite.setCursor ( 0, gridHeight + yOrigin ); + sSprite.printf ( "%4i", minGrid ); sSprite.setTextColor ( WHITE ); sSprite.setCursor ( 0, HALF_CHAR_H * 8 ); @@ -1493,6 +1558,11 @@ double fStop; fCenter = (double) ((( startFreq_IF + stopFreq_IF ) / 2.0 ) / 1000000.0 ); fStop = stopFreq_IF/1000000.0; } + else if (tinySA_mode == RX_SWEEP) { + fStart = startFreq_RX/1000000.0; + fCenter = (double) ((( startFreq_RX + stopFreq_RX ) / 2.0 ) / 1000000.0 ); + fStop = stopFreq_RX/1000000.0; + } else { fStart = (( (double)setting.ScanStart )/ 1000000.0); // Start freq @@ -1503,16 +1573,16 @@ double fStop; if ( old_startFreq != fStart || old_stopFreq != fStop ) { - tft.fillRect ( X_ORIGIN, SCREEN_HEIGHT - - CHAR_HEIGHT, SCREEN_WIDTH - X_ORIGIN - 1, SCREEN_HEIGHT - 1, BLACK ); + tft.fillRect ( xOrigin, SCREEN_HEIGHT - + CHAR_HEIGHT, SCREEN_WIDTH - xOrigin - 1, SCREEN_HEIGHT - 1, BLACK ); // Show operating mode - tft.setCursor ( X_ORIGIN + 50, SCREEN_HEIGHT - CHAR_HEIGHT ); + tft.setCursor ( xOrigin + 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.setCursor ( xOrigin + 2, SCREEN_HEIGHT - CHAR_HEIGHT ); tft.print ( fStart ); // tft.print( "MHz" ); @@ -1525,7 +1595,7 @@ double fStop; /* * Show the center frequency: */ - tft.setCursor ( SCREEN_WIDTH / 2 - 40 + X_ORIGIN, SCREEN_HEIGHT - CHAR_HEIGHT ); + tft.setCursor ( SCREEN_WIDTH / 2 - 40 + xOrigin, SCREEN_HEIGHT - CHAR_HEIGHT ); tft.print ( fCenter ); tft.print ( "(MHz)" ); @@ -1583,7 +1653,7 @@ double fStop; int x = tSprite.width () - 45; tSprite.setTextColor ( WHITE ); - tSprite.pushSprite ( X_ORIGIN, 0 ); // Write sprite to the display + tSprite.pushSprite ( xOrigin, 0 ); // Write sprite to the display updateSidebar = false; } // End of "DisplayInfo" @@ -1596,12 +1666,12 @@ double fStop; void DrawFullCheckerBoard() { - for ( int p=0; p 0 ) - img.pushSprite( X_ORIGIN+p-1, Y_ORIGIN ); + img.pushSprite( xOrigin+p-1, yOrigin ); } // Serial.println ( "DrawFullCheckerBoard" ); @@ -1612,7 +1682,7 @@ void 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 + * The img 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. */ @@ -1625,24 +1695,24 @@ void DrawCheckerBoard ( int x ) if ( x == 1 ) { img.fillSprite ( BLACK ); // Clear the sprite - img.drawFastVLine ( 0, 0, GRID_HEIGHT, DARKGREY ); // Draw vertical line at edge + img.drawFastVLine ( 0, 0, gridHeight, 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 + for ( int y=0; y <= yGrid; y++ ) + img.drawPixel ( 1, y*yDelta, DARKGREY ); // Draw the horizontal grid lines } else { - img.setScrollRect ( 0, 0, 2,GRID_HEIGHT, BLACK ); // Scroll the whole sprite + img.setScrollRect ( 0, 0, 2,gridHeight, 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 ); + if (( x % xDelta ) == 0 ) // Need a vertical line here + img.drawFastVLine ( 1, 0, gridHeight, DARKGREY ); else - for ( int y = 0; y <= Y_GRID; y++ ) - img.drawPixel ( 1, y * DELTA_Y, DARKGREY ); // Draw the horizontal grid lines + for ( int y = 0; y <= yGrid; y++ ) + img.drawPixel ( 1, y * yDelta, DARKGREY ); // Draw the horizontal grid lines } } @@ -1654,18 +1724,18 @@ void DrawCheckerBoard ( int x ) uint16_t rssiToImgY ( uint8_t rSSI ) { - int delta = setting.MaxGrid - setting.MinGrid; + int delta = maxGrid - minGrid; double y = rssiTodBm ( rSSI ); - y = ( y - setting.MinGrid ) * GRID_HEIGHT / delta; + y = ( y - minGrid ) * gridHeight / delta; - if ( y >= GRID_HEIGHT ) - y = GRID_HEIGHT-1; + if ( y >= gridHeight ) + y = gridHeight-1; if ( y < 0 ) y = 0; - return GRID_HEIGHT - 1 - (int) y; + return gridHeight - 1 - (int) y; } @@ -1679,6 +1749,16 @@ double rssiTodBm ( uint8_t rSSI ) } +/* + * Function to convert rSSi to dBm + */ + +uint8_t dBmToRSSI ( double dBm ) +{ + return ( 2 * ( dBm - setting.Attenuate + 120.0 - setting.LevelOffset ) ); +} + + /* * "DisplayPoint" - Display a point on the chart. * @@ -1693,28 +1773,28 @@ void DisplayPoint ( uint8_t* data, int i, int color ) return; int lastPoint = i - 1; - int delta = setting.MaxGrid - setting.MinGrid; + int delta = maxGrid - minGrid; double f0 = (( data[i] / 2.0 + setting.Attenuate ) - 120.0 ) + setting.LevelOffset; // Current point - f0 = ( f0 - setting.MinGrid ) * GRID_HEIGHT / delta; + f0 = ( f0 - minGrid ) * gridHeight / delta; - if ( f0 >= GRID_HEIGHT ) - f0 = GRID_HEIGHT-1; + if ( f0 >= gridHeight ) + f0 = gridHeight-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; + f1 = ( f1 - minGrid ) * gridHeight / delta; - if ( f1 >= GRID_HEIGHT ) - f1 = GRID_HEIGHT-1; + if ( f1 >= gridHeight ) + f1 = gridHeight-1; if ( f1 < 0 ) f1 = 0; - int Y0 = GRID_HEIGHT - 1 - (int) f0; - int Y1 = GRID_HEIGHT - 1 - (int) f1; + int Y0 = gridHeight - 1 - (int) f0; + int Y1 = gridHeight - 1 - (int) f1; img.drawLine ( 0, Y1, 1, Y0, color ); } @@ -1735,26 +1815,26 @@ void displayGainPoint ( unsigned char *data, int i, int color ) // Serial.printf ( "gain %f \n", f ); - f0 = ( f0 ) * GRID_HEIGHT / delta; + f0 = ( f0 ) * gridHeight / delta; - if ( f0 >= GRID_HEIGHT ) - f0 = GRID_HEIGHT - 1; + if ( f0 >= gridHeight ) + f0 = gridHeight - 1; if ( f0 < 0 ) f0 = 0; double f1 = ( data[lastPoint] ); - f1 = ( f1 ) * GRID_HEIGHT / delta; + f1 = ( f1 ) * gridHeight / delta; - if ( f1 >= GRID_HEIGHT ) - f1 = GRID_HEIGHT - 1; + if ( f1 >= gridHeight ) + f1 = gridHeight - 1; if ( f1 < 0 ) f1 = 0; - int Y0 = GRID_HEIGHT - 1 - (int) f0; - int Y1 = GRID_HEIGHT - 1 - (int) f1; + int Y0 = gridHeight - 1 - (int) f0; + int Y1 = gridHeight - 1 - (int) f1; img.drawLine ( 0, Y1, 1, Y0, color ); } @@ -1773,7 +1853,7 @@ 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.createSprite(CHAR_WIDTH * 2, gridHeight); gainScaleSprite.fillSprite(BLACK); gainScaleSprite.setPivot( 0, 0 ); gainScaleSprite.setTextSize ( 1 ); @@ -1790,11 +1870,60 @@ void CreateGainScale () for ( int i = 0; i < 5; i++ ) { - gainScaleSprite.setCursor ( 0, 2 + ( DELTA_Y * i * 2 )); + gainScaleSprite.setCursor ( 0, 2 + ( yDelta * i * 2 )); gainScaleSprite.print ( 50 - ( i * 10 )); } } + +/* + * "CreateGridScale" puts the decibel values for the main trace into a sprite for + * display at the left side of the Bandscope grid. + * The gainScaleSprite is reused as there in no need for a gain trace on the bandscope + * + * NOTE Due to temporary error in TFT_eSPI library the inverted colour is used + * so TFT_BLUE actually results in TFT_GREEN! + */ + +void CreateGridScale () +{ + gainScaleSprite.deleteSprite(); + gainScaleSprite.setAttribute ( PSRAM_ENABLE, false ); + gainScaleSprite.setColorDepth ( 16 ); // Using 16 bit (RGB565) colors + gainScaleSprite.createSprite(CHAR_WIDTH * 4, gridHeight); + gainScaleSprite.fillSprite(BLACK); + gainScaleSprite.setPivot( 0, 0 ); + gainScaleSprite.setTextSize ( 1 ); + gainScaleSprite.setTextColor ( TFT_WHITE ); + int w = gainScaleSprite.width(); + +// Serial.printf("Create gain scale - get sprite width %i \n", w); + + if ( w != CHAR_WIDTH * 4 ) + { + Serial.println ( "Error - no gain scale sprite" ); + return; + } + + // Markers at every other line, there are 10 lines + int16_t valueInterval = (setting.BandscopeMaxGrid - setting.BandscopeMinGrid) / yGrid * 2 ; + int16_t v = setting.BandscopeMaxGrid; + int16_t yoffset; + + for ( int i = 0; i < 5; i++ ) + { + if (i > 0) + yoffset = -3; + else + yoffset = 0; + gainScaleSprite.setCursor ( 0, yoffset + ( yDelta * i * 2 )); + gainScaleSprite.printf ( "%4i", v ); + v = v - valueInterval; + } +} + + + /* * "ledcAnalogWrite" - Arduino like analogWrite used for PWM control of backlight. * diff --git a/simpleSA_wifi.cpp b/simpleSA_wifi.cpp index 7dc8c61..a40e13c 100644 --- a/simpleSA_wifi.cpp +++ b/simpleSA_wifi.cpp @@ -16,8 +16,23 @@ #include "simpleSA_wifi.h" // WiFi definitions -#include "Si4432.h" // Si4432 definitions -#include "Cmd.h" // Command processing functions +#include "si4432.h" // Si4432 definitions +#include "cmd.h" // Command processing functions + +/* + * Variables to determine size of grid and waterfall + * In Bandscope mode the grid is reduced. In future it may be possible to add + * a waterfall to the main sweep, but not yet + */ +extern uint16_t gridHeight; +extern uint16_t gridWidth; +extern uint16_t yGrid; // no of grid divisions +extern uint16_t yDelta; // no of points/division +extern uint16_t xGrid; +extern uint16_t displayPoints; +extern uint16_t xDelta; +extern uint16_t waterfallHeight; + extern int bpfCount; // Number of elements in the bandpassFilters array extern int updateSidebar; // Flag to indicate no of clients has changed @@ -376,7 +391,7 @@ void onGetScan ( AsyncWebServerRequest *request ) response->print ( "" ); response->println ( "" ); - for( int i = 0; i < DISPLAY_POINTS-1; i++ ) // For each data point + for( int i = 0; i < displayPoints-1; i++ ) // For each data point { // Serial.printf ( " %i\n",myFreq[i], myData[i], i ); response->printf ( "

\n", myFreq[i], myData[i] ); @@ -394,8 +409,8 @@ void onGetScan ( AsyncWebServerRequest *request ) void onGetGainSweep ( AsyncWebServerRequest *request ) { - size_t bufferSize = JSON_ARRAY_SIZE ( DISPLAY_POINTS ) - + JSON_OBJECT_SIZE ( 1 ) + DISPLAY_POINTS * JSON_OBJECT_SIZE ( 2 ); + size_t bufferSize = JSON_ARRAY_SIZE ( SCREEN_WIDTH ) + + JSON_OBJECT_SIZE ( 1 ) + SCREEN_WIDTH * JSON_OBJECT_SIZE ( 2 ); AsyncJsonResponse * response = new AsyncJsonResponse ( false, bufferSize ); // response->addHeader ( "Server","ESP Async Web Server" ); @@ -407,7 +422,7 @@ void onGetGainSweep ( AsyncWebServerRequest *request ) * Add the objects to the array */ - for ( int i = 0; i < DISPLAY_POINTS; i++ ) // For each data point + for ( int i = 0; i < displayPoints; 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 @@ -426,14 +441,14 @@ void onGetGainSweep ( AsyncWebServerRequest *request ) void onGetSweep ( AsyncWebServerRequest *request ) { - size_t bufferSize = JSON_ARRAY_SIZE ( DISPLAY_POINTS ) - + JSON_OBJECT_SIZE ( 14 ) + DISPLAY_POINTS * JSON_OBJECT_SIZE ( 2 ); + size_t bufferSize = JSON_ARRAY_SIZE ( SCREEN_WIDTH ) + + JSON_OBJECT_SIZE ( 14 ) + SCREEN_WIDTH * JSON_OBJECT_SIZE ( 2 ); AsyncJsonResponse * response = new AsyncJsonResponse ( false, bufferSize ); JsonObject root = response->getRoot(); - root["dispPoints"] = DISPLAY_POINTS; + root["dispPoints"] = displayPoints; root["start"] = setting.ScanStart / 1000.0; root["stop"] = setting.ScanStop / 1000.0; root["IF"] = setting.IF_Freq / 1000000.0; @@ -454,7 +469,7 @@ void onGetSweep ( AsyncWebServerRequest *request ) JsonArray Points = root.createNestedArray ( "Points" ); // Add Points array - for ( int i = 0; i < DISPLAY_POINTS; i++ ) //For each data point + for ( int i = 0; i < displayPoints; 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 @@ -477,7 +492,7 @@ void onGetSettings (AsyncWebServerRequest *request) JsonObject root = response->getRoot(); root["mType"] = "Settings"; - root["dispPoints"] = DISPLAY_POINTS; + root["dispPoints"] = displayPoints; root["start"] = setting.ScanStart / 1000.0; root["stop"] = setting.ScanStop / 1000.0; root["IF"] = setting.IF_Freq / 1000000.0; @@ -507,12 +522,12 @@ void onGetSettings (AsyncWebServerRequest *request) */ void pushSettings () { -size_t capacity = JSON_ARRAY_SIZE ( DISPLAY_POINTS ) - + DISPLAY_POINTS*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 13 ); +size_t capacity = JSON_ARRAY_SIZE ( SCREEN_WIDTH ) + + SCREEN_WIDTH*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["dispPoints"] = displayPoints; jsonDocument["start"] = setting.ScanStart / 1000.0; jsonDocument["stop"] = setting.ScanStop / 1000.0; jsonDocument["IF"] = setting.IF_Freq / 1000000.0; @@ -551,12 +566,12 @@ static DynamicJsonDocument jsonDocument ( capacity ); // buffer for json data to */ void pushIFSweepSettings () { -size_t capacity = JSON_ARRAY_SIZE ( DISPLAY_POINTS ) - + DISPLAY_POINTS*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 13 ); +size_t capacity = JSON_ARRAY_SIZE ( SCREEN_WIDTH ) + + SCREEN_WIDTH*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["dispPoints"] = displayPoints; jsonDocument["start"] = startFreq_IF / 1000.0; jsonDocument["stop"] = stopFreq_IF / 1000.0; jsonDocument["IF"] = sigFreq_IF / 1000000.0; @@ -594,8 +609,8 @@ static DynamicJsonDocument jsonDocument ( capacity ); // buffer for json data to */ void pushBandscopeSettings () { -size_t capacity = JSON_ARRAY_SIZE ( DISPLAY_POINTS ) - + DISPLAY_POINTS*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 13 ); +size_t capacity = JSON_ARRAY_SIZE ( SCREEN_WIDTH ) + + SCREEN_WIDTH*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"; diff --git a/simpleSA_wifi.h b/simpleSA_wifi.h index eb60e1e..ea6f972 100644 --- a/simpleSA_wifi.h +++ b/simpleSA_wifi.h @@ -9,7 +9,7 @@ #include "Arduino.h" // Basic Arduino definitions #include "simpleSA.h" // Program definitions -#include "Si4432.h" // RF module definitions +#include "si4432.h" // RF module definitions #include // WiFi library #include @@ -71,11 +71,11 @@ 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 uint8_t myData[SCREEN_WIDTH+1]; + extern uint8_t myStorage[SCREEN_WIDTH+1]; + extern uint8_t myActual[SCREEN_WIDTH+1]; + extern uint8_t myGain[SCREEN_WIDTH+1]; // M0WID addition to record preamp gain + extern uint32_t myFreq[SCREEN_WIDTH+1]; // M0WID addition to store frequency for XML file extern WebSocketsServer webSocket; // Initiated in TinySA.ino diff --git a/ui.cpp b/ui.cpp index c24cd6e..152fee8 100644 --- a/ui.cpp +++ b/ui.cpp @@ -30,12 +30,12 @@ #include // SPI Bus handling library #include "simpleSA.h" #include "ui.h" // User interface definitions and prototypes -#include "Cmd.h" // Command processing functions +#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 +#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 @@ -94,8 +94,8 @@ typedef struct { // 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; + double value; // For editing at numeric input area + double previous_value; } uistat_t; @@ -107,8 +107,8 @@ uistat_t uistat = { 6, // digit - See note above 0, // digit_mode 0, // current_trace - 0, // value - 0 // previous_value + 0.0, // value + 0.0 // previous_value }; @@ -197,7 +197,8 @@ uint8_t operation_requested = OP_NONE; // No operations so far 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 }; + KM_IFSIG, KM_TGOFFSET, KM_TGLO_DRIVE, KM_TGIF_DRIVE, KM_BANDSCOPESTART, + KM_WFMIN, KM_WFGAIN, KM_BANDSCOPELEVEL, KM_RXSPAN, KM_RXSIG, KM_RBW }; /* @@ -214,7 +215,8 @@ 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" + "IF Sig Freq", "TG OFFSET", "TG LO Drive", "TG IF Drive", "START", + "WF MIN", "WF GAIN", "REF LEVEL", "RX SPAN", "RX Sig Freq", "RBW" }; @@ -272,10 +274,18 @@ 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_RX_sweep_cb ( int item ); 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 + +// forward declarations for bandscope menu items +static void menu_BandscopeStart_cb ( int item ); +static void menu_BandscopeRbw_cb ( int item ); +static void menu_BandscopeSpan_cb ( int item ); +static void menu_BandscopeLevel_cb ( int item ); +static void menu_WaterfallMin_cb ( int item ); +static void menu_WaterfallGain_cb ( int item ); 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 @@ -433,8 +443,9 @@ 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_FUNC, "\2IF\0SWEEP", menu_mode_cb ), + Menuitem ( MT_FUNC, "\2RX\0SWEEP", menu_mode_cb ), Menuitem ( MT_BACK, "<-BACK" ), // Next level up Menuitem ( MT_END ) // End marker }; @@ -730,15 +741,51 @@ static Menuitem menu_IFsweep_top[] = // This is the main "IF_SWEEP" menu }; -static Menuitem menu_Bandscope_top[] = // This is the main "IF_SWEEP" menu +static Menuitem menu_RXsweep_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_FUNC, "\2SWEEP\0SPAN", menu_RX_sweep_cb ), + Menuitem ( MT_FUNC, "\2SWEEP\0SIG", menu_RX_sweep_cb ), + Menuitem ( MT_FUNC, "RBW", menu_RX_sweep_cb ), + Menuitem ( MT_MENU, "REFERENCE", menu_refer ), // Select GPIO2 reference frequency Menuitem ( MT_END ) // End marker }; +static Menuitem menu_BandscopeSpan[] = // Badscope Span settings +{ + Menuitem ( MT_FUNC, "200kHz", menu_BandscopeSpan_cb ), + Menuitem ( MT_FUNC, "400kHz", menu_BandscopeSpan_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + Menuitem ( MT_END ) // End marker +}; + + +static Menuitem menu_BandscopeRBW[] = // Resolution bandwidth settings +{ + Menuitem ( MT_FUNC, "2.6kHz", menu_BandscopeRbw_cb ), // In auto mode, there are many + Menuitem ( MT_FUNC, "2.8kHz", menu_BandscopeRbw_cb ), // more available settings that + Menuitem ( MT_FUNC, "3.1kHz", menu_BandscopeRbw_cb ), // are roughly the sweep range + Menuitem ( MT_FUNC, "3.7kHz", menu_BandscopeRbw_cb ), // divided by 300. + Menuitem ( MT_FUNC, "4.2kHz", menu_BandscopeRbw_cb ), + Menuitem ( MT_FUNC, "5.4kHz", menu_BandscopeRbw_cb ), + Menuitem ( MT_BACK, "<-BACK" ), // Next level up + 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_BandscopeStart_cb ), + Menuitem ( MT_MENU, "\2SWEEP\0SPAN", menu_BandscopeSpan ), + Menuitem ( MT_MENU, "RBW", menu_BandscopeRBW ), + Menuitem ( MT_FUNC, "\2REF\0LEVEL", menu_BandscopeLevel_cb ), // Set top line of the grid + Menuitem ( MT_FUNC, "\2W'FALL\0MIN", menu_WaterfallMin_cb ), + Menuitem ( MT_FUNC, "\2W'FALL\0GAIN", menu_WaterfallGain_cb ), + Menuitem ( MT_END ) // End marker +}; + /* * And last but not least the main menu! @@ -1111,13 +1158,13 @@ void ShowSplash ( void ) tft.setTextColor ( MAGENTA ); tft.setFreeFont ( &FreeSerifBoldItalic18pt7b ); // Select Free Serif 9 point font - tft.drawString ( "TinySA for ESP32", 160, 20 ); + tft.drawString ( "simpleSA 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.drawString ( "Version 0.01", 160, 100 ); + tft.drawString ( "Original tinySA by Erik (PD0EK)", 160, 120 ); tft.setTextDatum ( TL_DATUM ); // Back to default top left @@ -1161,15 +1208,22 @@ void menu_mode_cb ( int item ) break; case 2: // Set IF Sweep mode - setMode(IF_SWEEP); + setMode(BANDSCOPE); ui_mode_normal (); // No menu displayed break; case 3: // Set IF Sweep mode - setMode(BANDSCOPE); + setMode(IF_SWEEP); ui_mode_normal (); // No menu displayed break; + + case 4: // Set RX Sweep mode + setMode(RX_SWEEP); + ui_mode_normal (); // No menu displayed + break; + } + changedSetting = true; } @@ -1395,7 +1449,7 @@ static void menu_tracking_cb (int item ) } /* - * "menu_autosettings_cb" seems to set all the defaults then clears the menu + * "menu_autosettings_cb" sets defaults then clears the menu * from the display. * * Modified in Version 2.5 by WA2FZW: @@ -1432,13 +1486,13 @@ static void menu_touch_cb ( int item ) { switch ( item ) { - case 0: // All these need symbols! + case 0: // Touch calibrate touch_cal_exec (); request_to_redraw_grid (); draw_menu (); break; - case 1: + case 1: // Touch Draw test touch_draw_test (); request_to_redraw_grid (); draw_menu (); @@ -1739,12 +1793,100 @@ static void menu_IF_sweep_cb ( int item ) ui_process_keypad (); } -static void menu_Bandscope_cb ( int item ) + +static void menu_RX_sweep_cb ( int item ) { - ui_mode_keypad ( item + KM_BANDSCOPESTART - 1 ); // item = 1 -> KM_BANDSCOPESTART, 2 -> KM_BANDSCOPESPAN + ui_mode_keypad ( item + KM_RXSPAN - 1 ); // item = 1 -> KM_RXSPAN, 2 -> KM_RXSIG ui_process_keypad (); } + + +/* + * ********************************************* + * BANDSCOPE MENU ITEMS + * ********************************************* + */ + + +/* + * Handles the Bandscope RBW menu item. + * + */ +static void menu_BandscopeRbw_cb ( int item ) +{ +const int rbwsel[] = { 0, 26, 28, 31, 37 , 42, 54 }; // Resolution bandwidth choices (in KHz * 10) + + SetBandscopeRBW ( rbwsel[item] ); + menu_move_back (); + ui_mode_normal (); +} + + +/* + * Set the span for the bandscope + */ +static void menu_BandscopeSpan_cb ( int item ) +{ + switch ( item ) + { + case 0: // 200kHz + setting.BandscopeSpan = 200000; + break; + + case 1: // 400kHz + setting.BandscopeSpan = 400000; + break; + } + + WriteSettings (); + menu_move_back (); + ui_mode_normal (); +} + + +/* + * Set the level of the top of the bandscope scale + */ +static void menu_BandscopeLevel_cb ( int item ) +{ + ui_mode_keypad ( KM_BANDSCOPELEVEL ); + ui_process_keypad (); +} + + +/* + * Set the min for the bandscope waterfall colours + */ +static void menu_WaterfallMin_cb ( int item ) +{ + ui_mode_keypad ( KM_WFMIN ); + ui_process_keypad (); +} + + +/* + * Set the gain for the bandscope waterfall colours + */ +static void menu_WaterfallGain_cb ( int item ) +{ + ui_mode_keypad ( KM_WFGAIN ); + ui_process_keypad (); +} + + + +/* + * Set the start frequency of the bandscope sweep + */ +static void menu_BandscopeStart_cb ( int item ) +{ + ui_mode_keypad ( KM_BANDSCOPESTART ); + ui_process_keypad (); +} + + + /* * "ensure_selection" - Validates that a menu selection is valid */ @@ -1982,7 +2124,13 @@ static const keypads_t * const keypads_mode_tbl[] = 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 + keypads_integer, // KM_WFMIN..........Waterfall min level (RSSI) + keypads_level, // KM_WFGAIN.........Waterfall Gain + keypads_level, // KM_BANDSCOPELEVEL.Grid reference level + keypads_freq, // KM_RXSPAN.........RX Sweep span frequency + keypads_freq, // KM_RXSIG..........RX Sweep signal frequency + keypads_freq // KM_RBW............RX Sweep RBW + }; @@ -2373,10 +2521,30 @@ static void fetch_numeric_target ( void ) uistat.value = setting.BandscopeStart; break; - case KM_BANDSCOPESPAN: - uistat.value = setting.BandscopeSpan; + case KM_WFMIN: + uistat.value = setting.WaterfallMin; + break; + + case KM_WFGAIN: + uistat.value = setting.WaterfallGain; + break; + + case KM_BANDSCOPELEVEL: + uistat.value = setting.BandscopeMaxGrid; + break; + + case KM_RXSPAN: + uistat.value = GetRXsweepSpan(); break; + case KM_RXSIG: + uistat.value = GetRXsweepSigFreq(); + break; + + case KM_RBW: + uistat.value = (double)setting.Bandwidth10 / 10.0; + break; + } uint32_t x = uistat.value; @@ -2388,7 +2556,7 @@ static void fetch_numeric_target ( void ) uistat.digit = n; uistat.previous_value = uistat.value; - Serial.printf("uistat previous value %f\n", uistat.previous_value ); +// Serial.printf("uistat previous value %f\n", uistat.previous_value ); } @@ -2792,11 +2960,51 @@ static int keypad_click ( int key ) break; - case KM_BANDSCOPESPAN: // Bandscope span entered? - SetBandscopeSpan (( int32_t ) value ); + case KM_WFMIN: // Bandscope span entered? + if ( (value >= 0) && (value <= 200 ) ) + setting.WaterfallMin = ( int16_t )value ; + else + { + DisplayError ( ERR_WARN, + "Invalid minimum level!", + "(0 - 200)", + NULL, + NULL ); + } break; + case KM_WFGAIN: // Bandscope span entered? + if ( (value >= 0.1) && (value <= 4.0 ) ) + setting.WaterfallGain = value ; + else + { + DisplayError ( ERR_WARN, + "Invalid gain!", + "(0.1 - 4)", + NULL, + NULL ); + } + break; + + + case KM_BANDSCOPELEVEL: // Bandscope level entered? + SetBandscopeLevel (( int32_t ) value ); + break; + + case KM_RXSPAN: // RX Span frequency entered? + Serial.printf( "Value = %f\n", value ); + SetRXsweepSpan (( int32_t ) value ); + break; + + case KM_RXSIG: // RX Signal frequency entered? + SetRXsweepSigFreq (( int32_t ) value ); + break; + + case KM_RBW: // RBW + SetRBW (( int32_t ) (value * 10) ); + break; + } // End of "switch" return KP_DONE; // Indicate finished with the keypad @@ -2990,41 +3198,46 @@ void UiProcessTouch ( void ) 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(); +/* + * Some touchscreen areas have fast access to the submenu levels, depends on the mode + */ + if ( setting.Mode == SA_LOW_RANGE ) + { + // 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 ) + StartSweepMenu(); + else if ( (touch_x < 30) && (touch_y < 60) ) + StartRBWMenu(); + else if ( (touch_x < 30) && (touch_y > CHAR_HEIGHT * 20 ) && ( touch_y < CHAR_HEIGHT * 22 ) ) + { + SetSpur (!setting.Spur); + return; + } + else if ( (touch_x < 30) ) + StartDisplayMenu(); + } selection = -1; // Switch menu mode bg = BLACK; // black background @@ -3160,6 +3373,21 @@ void ResetIFsweepMenuStack (void) ui_mode_normal (); } +/* + * Resets the menu stack to root level for RX_SWEEP mode + */ +void ResetRXsweepMenuStack (void) +{ + tft.unloadFont(); + selection = -1; // Switch menu mode + menu_current_level = 0; + menu_stack[0] = menu_RXsweep_top; + menu_stack[1] = NULL; + menu_stack[2] = NULL; + menu_stack[3] = NULL; + if (ui_mode != UI_NORMAL) + ui_mode_normal (); +} /*