/* * "Cmd.cpp" was added in Version 2.0 by John Price (WA2FZW) * * This file contains the functions to read a command and the associated data * from the serial input and all the functions necessary to process the * commands. * * The functions to read and interpret the commands were stripped out of the * "loop" function in the main program file. As it currently exists, the command * structure has been modified to be more human friendly, and will no longer work * with Erik's "TintSA.exe" program, which has been replaced with a web page * interface which is much more flexibile. * * The other functions in here all support the main command interpreter and are * also used by the "ui.cpp" and "TinySA_wifi.cpp" modules. Eventually, we hope * that those modules will also be able to use the "ProcessCommand" function in * here in place of much redundant code in those modules. */ #include "cmd.h" // Our associated header #include "preferences.h" // For "SAVE" & "RECALL" #include "marker.h" // Marker class definition /* * These are the objects for the PE4302 and Si4432 modules. They are created in the * main program file: */ extern PE4302 att; // PE4302 Attenuator object extern Si4432 rcvr; // Si4432 Receiver object extern Si4432 xmit; // Si4432 Transmitter object #ifdef TG_IF_INSTALLED extern Si4432 tg_if; // Si4432 Transmitter object #endif #ifdef TG_LO_INSTALLED extern Si4432 tg_lo; // Si4432 Transmitter object #endif /* * Global variables defined in the main program file: */ extern settings_t setting; // Structure to track & save settings extern bandpassFilter_t bandpassFilters[]; // RBW Setting data extern uint16_t bpfCount; // Number of entries in "bandpassFilters" extern int changedSetting; // Something in "setting" changed extern uint16_t bpfCalibrate; // set true if a SI4432 bandpass filter calibration run is taking place extern uint16_t tinySA_mode; /* * 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 uint16_t bpfIndex; // RBW bandpass Filter in use extern unsigned long offsetDelayTime; // In microseconds extern unsigned long wiFiTargetTime; extern uint16_t wiFiPoints; extern unsigned long websocketInterval; // time in microseconds between websocket polls if no wifi client extern int VFO; // TX/RX Si4432 selector extern uint16_t steps; // Number of frequency steps in the sweep (not really used) extern bool setActualPowerRequested; extern float actualPower; extern uint16_t oldPeakLevel; // Current maximum signal level (was peakLevel) G3ZQC int32_t frequency0 = 0; // Used in setting sweep configuration int32_t frequency1 = 100000000; extern uint32_t lastStart; // Last value of the sweep start frequency extern uint32_t lastStop; // Last value of the sweep end frequency extern uint32_t startFreq_IF; // Last value of the IF sweep start frequency extern uint32_t stopFreq_IF; // Last value of the IF sweep end frequency extern uint32_t sigFreq_IF; // Last value of the IF sweep end frequency extern uint32_t startFreq_RX; // Last value of the RX sweep start frequency extern uint32_t stopFreq_RX; // Last value of the RX sweep end frequency extern uint32_t sigFreq_RX; // Last value of the RX 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 extern uint16_t sigGenOutputOn; // signal generator state - 0 = off, 1 = on /* * Functions still in the main program file. Maybe some need to move here? */ extern void DisplayInfo (); extern void DisplayError ( uint8_t severity, const char *line1, const char *line2, const char *line3, const char *line4 ); extern void RedrawHisto (); extern void pushSettings (); extern void pushBandscopeSettings (); extern void initSweepLow (); extern void initSigLow (); extern void initIF_Sweep (); extern void setMode (uint16_t newMode); 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. */ msg_t msgTable[] = { { "START", MSG_START }, // Set sweep start frequency { "STOP", MSG_STOP }, // Set sweep stop frequency { "CENTER", MSG_CENTER }, // Set sweep center frequency { "SPAN", MSG_SPAN }, // Set sweep range { "FOCUS", MSG_FOCUS }, // Set sweep center frequency with narrow bandwidth { "PREAMP", MSG_PREAMP }, // Set the receiver preamp gain { "GAIN", MSG_PREAMP }, // Alternate receiver preamp gain { "DRIVE", MSG_DRIVE }, // Set transmitter (LO) output level { "FREQ", MSG_VFO_FREQ }, // Set the frequency for the selected VFO { "ATTEN", MSG_ATTEN }, // Set the PE4301 attenuation { "HELP", MSG_HELP }, // Display the command menu { "?", MSG_HELP }, // Display the command menu { "STEPS", MSG_STEPS }, // Set or get number of sweep points (not used) { "DELAY", MSG_DELAY }, // Set or get delay time between sweep readings { "VFO", MSG_VFO }, // Set or get the currently selected VFO { "RBW", MSG_RBW }, // Set or get the current resolution bandwidth { "REGDUMP", MSG_REG_DUMP }, // Print register values for the selected VFO { "RSSI", MSG_RSSI }, // Show RSSI values { "QUIT", MSG_QUIT }, // Stop RSSI readings { "REGISTER", MSG_SET_REG }, // Set or get the value of a specific register for the selected VFO { "SAVE", MSG_SAVE }, // Save scan configuration { "RECALL", MSG_RECALL }, // Recall a saved scan configuration { "REF_FREQ", MSG_GPIO2 }, // Set transmitter GPIO2 frequency { "TUNE", MSG_TUNE }, // Tune the selected Si4431 (VFO) { "CONFIG_SAVE", MSG_CONFIG }, // Save "config" structure { "ACTUAL_PWR", MSG_ACT_PWR }, // Calibrate the indicated power level { "IF_FREQ", MSG_IF_FREQ }, // Change the IF (receiver) frequency { "TRACES", MSG_TRACES }, // Turn traces on the display on or off { "GRIDREF", MSG_GRID }, // Set dB value for the top line of the grid { "SCALE", MSG_SCALE }, // Set dB/horizontal line value for the grid { "PAUSE", MSG_PAUSE }, // Pause at end of sweep - toggled { "SALO", MSG_SWEEPLO }, // Sweep low range mode { "SGLO", MSG_SIGLO }, // Signal generator low range mode { "IFSWEEP", MSG_IF_SWEEP }, // IF sweep mode { "BANDSCOPE", MSG_BANDSCOPE }, // BANDSCOPE mode { "MARKER", MSG_MARKER }, // Set marker status and color { "SPUR", MSG_SPUR }, // Set Spur reduction on/off { "WIFITIME", MSG_WIFI_UPDATE }, // Set WiFi Update target time { "WIFIPTS", MSG_WIFI_POINTS }, // Set WiFi points { "SKTINT", MSG_WEBSKT_INTERVAL }, // Set WebSocket interval { "SGRXDRIVE", MSG_SG_RX_DRIVE }, // Set Signal Generator RX Drive level { "SGLODRIVE", MSG_SG_LO_DRIVE }, // Set Signal Generator LO Drive level { "TGIFDRIVE", MSG_TG_IF_DRIVE }, // Set Track Generator IF Drive level { "TGLODRIVE", MSG_TG_LO_DRIVE }, // Set Track Generator LO Drive level { "IFSIGNAL", MSG_IFSIGNAL }, // IF sweep signal frequency { "TGOFFSET", MSG_TGOFFSET }, // Offset TG IF from SA IF to reduce pass through { "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 { "EXTGAIN", MSG_EXTGAIN }, // External gain or attenuation { "SGON", MSG_SGON }, // turn on signal generator output { "SGOFF", MSG_SGOFF }, // turn off signal generator output { "SGFREQ", MSG_SGFREQ }, // Set Signal Generator Frequency { "TRACKON", MSG_TGON }, // turn on tracking generator output { "TRACKOFF", MSG_TGOFF }, // turn off tracking generator output { "TRACKSIG", MSG_TGSIG }, // turn off tracking generator output { "TGFREQ", MSG_TGFREQ }, // Set Track gen freq for sig gen mode { "BPFCAL", MSG_BPFCAL }, // Start bandpass filter calibration { "OTA", MSG_OTA }, // Select OTA Update Mode { "", MSG_NONE } // Unrecognized command }; int msgCount = ELEMENTS ( msgTable); /* * "ShowMenu" displays the command menu on the USB output (Arduino IDE's Serial * Monitor, or some other terminal emulator program). */ void ShowMenu () { Serial.printf ( "\nsimpleSA Spectrum Analyzer %s - User Commands:\n\n", PROGRAM_VERSION ); Serial.println ( "Sweep Settings:\n" ); Serial.print ( " START.........Sweep start frequency, currently: " ); Serial.println ( FormatFrequency ( setting.ScanStart )); Serial.print ( " STOP..........Sweep stop frequency, currently: " ); Serial.println ( FormatFrequency ( setting.ScanStop )); Serial.print ( " CENTER........Sweep center frequency, currently: " ); Serial.println ( FormatFrequency ( GetSweepCenter() )); Serial.print ( " SPAN..........Sweep frequency span, currently: " ); Serial.println ( FormatFrequency ( setting.ScanStop - setting.ScanStart )); Serial.println ( " MARKER........It's complicated; read the documentation!" ); Serial.println ( " FOCUS.........Set single frequency mode with narrow bandwidth" ); Serial.printf ( " RBW...........Set or get resolution bandwidth (RBW); currently: %.1f KHz\n", bandwidth ); Serial.printf ( " ATTEN.........Set or get the attenuator setting; currently: %ddB\n", setting.Attenuate ); Serial.printf ( " EXTGAIN.......Set or get the external gain setting; currently: %ddB\n", setting.ExternalGain ); Serial.printf ( " SPUR......... Turn Spur Reduction 'ON' or 'OFF'; currently: %s\n", setting.Spur ? "ON" : "OFF" ); Serial.println ( " PAUSE.........Pause or resume the sweep" ); Serial.println ( "\nMode selection:\n" ); Serial.println ( " SALO..........Set to analyse mode low frequency range" ); Serial.println ( " SGLO..........Set to signal generator mode low frequency range" ); Serial.println ( " IFSWEEP.......Set to IF Sweep mode to analyse the TinySA SAW filters" ); Serial.println ( " RXSWEEP.......Set to RX Sweep mode to analyse the TinySA FIR filters" ); Serial.println ( " BANDSCOPE.....Set to BANDSCOPE mode" ); Serial.println ( " OTA...........Set to OTA update mode to download firmware or SPIFFS over wifi" ); Serial.println ( "\nDisplay Options:\n" ); Serial.println ( " TRACES........Turn display traces on or off ['GAIN' or 'dB']" ); Serial.printf ( " PREAMP/GAIN...Set or get the receiver preamp gain\n" ); Serial.println ( " See documentation for allowed values" ); Serial.printf ( " GRIDREF.......Set the grid reference level; currently: %ddB\n", setting.MaxGrid ); Serial.printf ( " SCALE.........Set the dB/horizontal line value; currently: %ddB\n", setting.PowerGrid ); Serial.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 ( "\nSignal Generator Commands:\n" ); Serial.println ( " SGON..........Turn on signal generator output" ); Serial.print ( " SGOFF.........Turn off signal generator output; currently " ); Serial.println ( sigGenOutputOn ); Serial.print ( " SGFREQ........Signal Generator frequency, currently: " ); Serial.println ( FormatFrequency ( sigGenSetting.Frequency )); Serial.print ( " SGLODRIVE.....Local oscillator drive level in signal generator mode [0 to 7]; currently: " ); Serial.println ( sigGenSetting.LO_Drive ); Serial.print ( " SGRXDRIVE.....RX SI4432 oscillator drive level in signal generator mode [0 to "); Serial.print ( RX_SI4432_MAX_DRIVE ); Serial.print ( "]; currently: " ); Serial.println ( sigGenSetting.RX_Drive ); #ifdef TG_IF_INSTALLED Serial.println ( "\nTracking Generator Commands:\n" ); Serial.println ( " TRACKON.......Turn on tracking generator output" ); #ifdef TG_LO_INSTALLED Serial.println ( " TRACKSIG......Turn on tracking generator signal generator output" ); #endif Serial.print ( " TRACKOFF......Turn off tracking generator output; currently " ); Serial.println ( trackGenSetting.Mode ); #ifdef TG_LO_INSTALLED Serial.print ( " TGLODRIVE.....Local oscillator drive level in tracking generator mode [0 to 7]; currently: " ); Serial.println ( trackGenSetting.LO_Drive ); #endif Serial.print ( " TGIFDRIVE.....Local oscillator drive level in tracking generator mode [0 to 7]; currently: " ); Serial.println ( trackGenSetting.IF_Drive ); #ifdef TG_LO_INSTALLED Serial.print ( " TGOFFSET......Tracking generator offset from SA IF; currently: " ); Serial.println ( trackGenSetting.Offset ); Serial.print ( " TGFREQ........Tracking generator signal generator frequency; currently: " ); Serial.println ( trackGenSetting.Frequency ); #endif #endif Serial.println ( "\nOther Commands:\n" ); Serial.print ( " DRIVE.........Local oscillator drive level [0 to 7]; currently: " ); Serial.println ( setting.Drive ); Serial.println ( " SAVE .........Save the current scan configuration [0 to 4]" ); Serial.println ( " RECALL........Recall a saved scan configuration [0 to 4]" ); Serial.println ( " FREQ..........Set or get the frequency for the selected VFO" ); Serial.println ( " HELP (or ?)...Show this menu" ); Serial.printf ( " STEPS.........Sweep samples; currently: %u\n", steps ); Serial.printf ( " DELAY.........Timestep in uS, currently: %uuS\n", delaytime ); Serial.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 ) Serial.println ( "RX (0)" ); else if (VFO == TX_4432 ) Serial.println ( "LO (1)" ); else if (VFO == TGIF_4432 ) Serial.println ( "TGIF (2)" ); else if (VFO == TGLO_4432 ) Serial.println ( "TGLO (3)" ); Serial.println ( "\nDebugging and Troubleshooting Commands:\n" ); Serial.printf ( " IF_FREQ.......Set the IF (receiver) frequency [433 to 435MHz]; currently: %s\n", FormatFrequency ( setting.IF_Freq )); Serial.println ( " REGDUMP.......Display a dump of all the registers for the selected VFO" ); Serial.println ( " RSSI..........Output RSSI readings until the 'Q' command is entered" ); Serial.println ( " or one time only ['C' or '1']; the default is once."); Serial.println ( " QUIT (or Q)...Terminate RSSI output" ); Serial.println ( " REGISTER......Read or write a Si4432 register for the selected VFO" ); Serial.println ( " REF_FREQ......Set or get the transmitter GPIO2 reference frequency" ); Serial.println ( " TUNE..........Tune the frequency of the selected VFO" ); Serial.println ( " ACTUAL_PWR....Calibrate the indicated power level" ); Serial.println ( " CONFIG_SAVE...Save the hardware configuration parameters" ); Serial.println ( " WiFiTIME......Target web page Chart Update time interval in ms" ); Serial.println ( " WiFiPTS.......No of points in each data chunk pushed to the web clients" ); Serial.println ( " SKTINT........Interval between check for websocket messages if no client connected" ); Serial.println ( " IFSIGNAL......Frequency of signal injected for IF Sweep (External or Ref)" ); Serial.println ( " BPFCAL........Start SI4432 bandpass filter calibration - RXSweep mode only"); Serial.println (); } /* * "CheckCommand" checks the serial input for any messages. If there is nothing * to read, it returns "false". * * If there is something to be read, the complete line of data is read into a * buffer. The read loop is set up to recognize either a return or newline character * as the line terminator and handles the case where both are used (as is possible * when using the Arduino IDE's Serial Monitor. The function also translates all * alpha characters to upper case. * * Once the command is read, the buffer is passed to "ProcessCommand" which does * whatever was requested. If the command is successfully executed, the function * returns "true" and returns "false" if the command was not successful. */ bool CheckCommand () { static char inBuff[80]; // Input buffer char c = 0; // Just one character static int16_t index = 0; // Index to the buffer if ( !Serial.available() ) // Any input? return false; // Nope! while ( Serial.available () ) // While data to read { c = toupper ( Serial.read () ); // Get next character Serial.print(c); // Echo //Serial.printf("(%02X)", c); // debug if ( ( c == '\r' ) || ( c == '\n' ) ) // End of the line? { Serial.println(); inBuff[index++] = '\0'; // Replace with a null //Serial.printf("EOL = %s \n", inBuff); // debug boolean result = ParseCommand ( inBuff ); // Process the command and return result index = 0; return result; } else // Not the end of the line { inBuff[index++] = c; // Save the character inBuff[index+1] = '\0'; // Make next character a null } if (index >= 78) { Serial.println("Serial Buffer Overun"); index = 0; break; } } return false; // return ParseCommand ( inBuff ); // Process the command and return result } /* * "ParseCommand" Translates the ASCII command text into a numerical value based * on the contents of the "msgTable" and separates the data portion of the message * (if any) into the "dataBuff". * * The process is a bit clever! The user only need to enter wnough characters of the * command to make it unique! For example, if the user simply entered the command as * "D", that could be interpreted as meaning "DRIVE" or "DELAY" (based on the contents * of the "msgTable" as I write this). But entering "DE" or "DR" is enough to distinguish * between the two. * * If the command is not found or is not unique, appropriate error messages are * diaplayed and the function returns false. * * If the command is identified uniquely, the "ProcessCommand" function is invoked * to complete the processing. */ bool ParseCommand ( char* inBuff ) // inBuff has full message { int messageLength; // Length of entire message int cmdLength; // Length of command string uint8_t cmdNumber; // Command translated to a number int cmdCount = 0; // Number of commands matching the input int inputIx = 0; // Input buffer index int cmdIx = 0; // Command string index int dataIx = 0; // Data string index char cmdString[10]; // Will contain the command string char dataBuff[20]; // Will contain the data portion of the message char fullCmd[20]; // Full command from table char c; // Just one character memset ( cmdString, 0, sizeof ( cmdString )); // Clear the command buffer memset ( dataBuff, 0, sizeof ( dataBuff )); // Clear the data buffer messageLength = strlen ( inBuff ); // Length of input if ( messageLength == 0 ) return false; while ( inputIx < messageLength ) { c = toupper ( inBuff[inputIx++] ); // Get a character if (( c != ' ' ) && ( c != ',' )) // If it's not a space or a comma cmdString[cmdIx++] = c; // Add to commmand string else // If it is a space or comma break; // On to the data part of the message } while ( inputIx < messageLength ) dataBuff[dataIx++] = inBuff[inputIx++]; // Copy rest of message to the data buffer cmdLength = strlen ( cmdString ); // Length of the command string cmdNumber = MSG_NONE; // No message found so far for ( inputIx = 0; inputIx < msgCount; inputIx++ ) // Search the msgTable { if ( strncmp ( cmdString, msgTable[inputIx].Name, cmdLength ) == 0 ) { cmdNumber = msgTable[inputIx].ID; // Get translation strcpy ( fullCmd, msgTable[inputIx].Name ); // And remember full command string cmdCount++; // Found at least one match if ( cmdCount > 1 ) // But did we find more than one? { Serial.printf ( "'%s' Is ambiguous!\n", cmdString ); return false; } } } if ( cmdNumber != MSG_NONE ) { // Serial.print ( "Command: " ); Serial.print ( fullCmd ); // Serial.print ( " = " ); Serial.println ( cmdNumber ); // Serial.print ( "Data: " ); Serial.println ( dataBuff ); return ( ProcessCommand ( cmdNumber, dataBuff )); } else Serial.printf ( "Command: '%s' not found!\n", cmdString ); return false; } /* * "ProcessCommand" parses the input buffer appropriately for the command, which is * (for now) a single letter in the 1st byte of the buffer. * * If the command is executed correctly, the function returns a "true" indication * and returns "false" if some error occurred. */ bool ProcessCommand ( uint8_t command, char* dataBuff ) { uint16_t dataIx = 0; // Data buffer index uint8_t dataLen; // Length of data buffer char tempBuff[40]; // Buffer for formatting responses int16_t tempIx = 0; // tempBuff index char c = 0; // A single character from the input int16_t addr; // Si4432 register address int16_t tempValue; // Temporary value of something int32_t tempFreq; // Temporary frequency float tempFloat; uint32_t freq1; // Several other temporary frequencies uint32_t freq2; // used in computing the center uint32_t freq3; // frequency and span range uint32_t freq4; int32_t signedFreq; int16_t attenuation; // Attenuation setting for the PE4302 uint8_t driveLevel[] = { 1, 2, 5, 8, 11, 14, 17, 20 }; // Drive level to dB translation uint8_t fTranslate[7] = { 30, 15, 10, 4, 3, 2, 1 }; // GPIO2 frequency table bool agcOn; // Used by "GetPreampGain" uint8_t reg69; // Ditto dataLen = strlen ( dataBuff ); // Length of the data string // Serial.print ( "Command: " ); Serial.println ( command ); // Serial.print ( "Data: " ); Serial.println ( dataBuff ); /* * The original code had a plethora of "if" statements to try to figure out what * command was entered; modified to a "switch" statement in Version 1.7. */ switch ( command ) { /* * The marker command is a bit more complicated than most. The format of the command * is: * * MARKER # ACTION * * where '#' is the marker number (1 to 4) and 'ACTION' is one of the following: * * ENABLE Turns it on * DISABLE Turns it off * WHITE Set the color to white * RED Set the color to red * BLUE etc. * GREEN * YELLOW * ORANGE * * Only the first letter of the 'ACTION' is necessary, so the command "MARKER 2 R" * would set the color of marker #2 to red. */ case MSG_MARKER: // Set marker status or color if ( dataLen < 1 ) // Less than 1; no number specified { Serial.println ( "No marker number specified" ); return false; } if ( !isDigit ( dataBuff[0] )) // Should be the marker number { Serial.println ( "No marker number specified" ); return false; } if ( dataLen < 3 ) // Has to be at least 3 characters { Serial.println ( "No marker action specified" ); return false; } tempValue = dataBuff[0] - 0x30 - 1; // Quick atoi conversion if ( tempValue >= MARKER_COUNT ) // Range check { Serial.printf ( "Illegal marker number: %i\n", tempValue + 1); return false; } if ( UpdateMarker ( tempValue, dataBuff[2] )) // Update the status return true; else { Serial.printf ( "Illegal marker action: %s\n", &dataBuff[2] ); return false; } /* * The "START" command sets the scan START frequency. and the "STOP" command * sets the scan STOP frequency. These are fairly straightforward! */ case MSG_START: // Set the start frequency if ( dataLen != 0 ) // Frequency specified? { tempFreq = ParseFrequency ( dataBuff ); // Then get the new frequency SetSweepStart ( tempFreq ); // Set it RedrawHisto(); //DisplayInfo (); // Restart scan? } Serial.printf ( "Scan start frequency: %s\n", FormatFrequency ( GetSweepStart () )); return true; case MSG_STOP: // Set the stop frequency if ( dataLen != 0 ) // Frequency specified? { tempFreq = ParseFrequency ( dataBuff ); // Get new frequency SetSweepStop ( tempFreq ); // And set it RedrawHisto (); } Serial.printf ( "Scan stop frequency: %s\n", FormatFrequency ( GetSweepStop () )); return true; /* * When the user sets the "CENTER" frequency, we're also going to set the "SPAN". But, * it's a bit tricky! * * The basic assumption is that we will set the "SPAN" equal to the selected "CENTER" * frequency. But if the "SPAN" limits would cause the "START" frequency to go negative * or cause the "STOP" frequency to exceed "STOP_MAX", the difference between the * "CENTER" frequency and whichever limit is exceeded will become 1/2 of the "SPAN". * * Of course, setting the "CENTER" frequency at either one of the limits is going to * result in a zero span. */ case MSG_CENTER: // Set the center frequency if ( dataLen != 0 ) // Frequency specified? { tempFreq = ParseFrequency ( dataBuff ); // Yes, get new frequency SetSweepCenter ( tempFreq, WIDE ); // Set it RedrawHisto (); } Serial.printf ( "Scan center frequency: %s\n", FormatFrequency ( GetSweepCenter ( ) )); return true; case MSG_SPAN: // Set the frequency span if ( dataLen != 0 ) // Frequency specified? { tempFreq = ParseFrequency ( dataBuff ); // Yes, get new frequency SetSweepSpan ( tempFreq ); // and set it RedrawHisto (); } Serial.printf ( "Sweep frequency span: %s\n", FormatFrequency ( GetSweepSpan ( ) )); return true; case MSG_FOCUS: // Set single frequency mode if ( dataLen != 0 ) // Frequency specified? { tempFreq = ParseFrequency ( dataBuff ); // Yes, get new frequency SetSweepCenter ( tempFreq, NARROW ); // Set it RedrawHisto (); } Serial.printf ( "Scan center frequency: %s\n", FormatFrequency ( GetSweepCenter () )); return true; /* * The "GRID" command sets the decibel value for the top grid line. * * If no number is entered, we report the current setting. */ case MSG_GRID: if ( dataLen != 0 ) // Value specified? { tempValue = atoi ( dataBuff ); // Yes, get grid reference value SetRefLevel ( tempValue ); // Set it DisplayInfo (); // Re display the scan } Serial.printf ( "Grid reference value: %ddB\n", setting.MaxGrid ); return true; /* * The "SCALE" command sets the decibel value spacing for each horizontal line on the grid. * Unlike the touch-screen equivalent command, here you can set any value you like as opposed * to chosing from a list of standard settings. * * If no number is entered, we report the current setting. */ case MSG_SCALE: if ( dataLen != 0 ) // Value specified? { tempValue = atoi ( dataBuff ); // Yes, get scale setting SetPowerGrid ( tempValue ); // Set it DisplayInfo (); // Re display the scan } Serial.printf ( "Grid scale: %ddB/div\n", setting.PowerGrid ); return true; /* * The "PREAMP" command sets the receiver Si4432 preamp gain. The way this works is pretty * strange and is explained in A440, but not very clearly! * * There are two ranges; low and high. The low range values are 5dB to 29dB in 3dB steps. * The high range goes from 25dB to 49dB in 3dB steps. Note there is some overlap! You can * also specify "AUTO" mode which turns on the AGC in the module. * * Rather than go through the hassle of figuring out if the value entered by the user is * one of the specific values allowed, we will allow the user to enter any value between * 5dB and 49dB and if it's not one of the legal values, we will use the next lower value. * * Note the ranges overlap, so if the requested value is greater than 23dB, we'll just use * the high range (in other words, we won't use 26dB or 29dB). */ case MSG_PREAMP: if ( dataLen != 0 ) // Any data? { if ( dataBuff[0] == 'A' ) // "AUTO" mode requested? tempValue = AGC_ON; // Turn on the AGC else if ( isDigit ( dataBuff[0] )) // It should be a number tempValue = atoi ( dataBuff ); // Convert it to a number SetPreampGain ( tempValue ); // Set the new value } // End of if ( dataLen != 0 ) tempValue = GetPreampGain ( &agcOn, ®69 ); if ( agcOn ) // If AGC is on Serial.println ( "Preamp AGC is on" ); else // No AGC, report real gain Serial.printf ( "Preamp gain is %ddB\n", tempValue ); pushSettings (); return true; /* * The "DRIVE" command sets the local oscillator "drive" level. */ case MSG_DRIVE: if ( dataLen != 0 ) // Drive level specified? { tempValue = atoi ( dataBuff ); // Yes, convert to a number if (( tempValue < MIN_DRIVE ) || ( tempValue > MAX_DRIVE )) Serial.printf ( "Invalid drive specification: %d\n", tempValue ); else // Set the transmitter drive level SetLoDrive ( tempValue ); // "SetLoDrive" saves the settings } Serial.printf ( "Drive: %d (+%udBm)\n", setting.Drive, driveLevel[setting.Drive] ); pushSettings (); return true; /* * Signal Generator On/Off commands * */ case MSG_SGON: SetSGState(1); return true; case MSG_SGOFF: SetSGState(0); return true; case MSG_SGFREQ: if ( dataLen != 0 ) // Frequency specified? { freq1 = ParseFrequency ( dataBuff ); // Yes, get new frequency SetSGFreq (freq1); } Serial.printf ( "Signal Generator frequency: %i\n", FormatFrequency ( freq1 )); return true; /* * The "SGLODRIVE" command sets the local oscillator "drive" level in signal generator mode. */ case MSG_SG_LO_DRIVE: if ( dataLen != 0 ) // Drive level specified? { tempValue = atoi ( dataBuff ); // Yes, convert to a number if (( tempValue < MIN_DRIVE ) || ( tempValue > MAX_DRIVE )) Serial.printf ( "Invalid LO drive: %d\n", tempValue ); else // Set the transmitter drive level SetSGLoDrive ( tempValue ); // "SetSGLoDrive" saves the settings } Serial.printf ( "SigGen LO Drive: %d (+%udBm)\n", sigGenSetting.LO_Drive, driveLevel[sigGenSetting.LO_Drive] ); //pushSettings (); return true; /* * The "SGRXDRIVE" command sets the receiver (IF) oscillator "drive" level in signal generator mode. */ case MSG_SG_RX_DRIVE: if ( dataLen != 0 ) // Drive level specified? { tempValue = atoi ( dataBuff ); // Yes, convert to a number if (( tempValue < MIN_DRIVE ) || ( tempValue > RX_SI4432_MAX_DRIVE )) Serial.printf ( "Invalid RX drive: %d - (%d-%d)\n", tempValue, MIN_DRIVE, RX_SI4432_MAX_DRIVE ); else // Set the transmitter drive level SetSGRxDrive ( tempValue ); // "SetSGRxDrive" saves the settings } Serial.printf ( "SigGen RX Drive: %d (+%udBm)\n", sigGenSetting.RX_Drive, driveLevel[sigGenSetting.RX_Drive] ); //pushSettings (); return true; /* * Tracking Generator On/Off commands * */ case MSG_TGON: SetTracking(1); return true; case MSG_TGOFF: SetTracking(0); return true; #ifdef TG_LO_INSTALLED case MSG_TGSIG: SetTracking(2); return true; /* * The "TGLODRIVE" command sets the local oscillator "drive" level in tracking generator mode. * (only valid if two SI4432 used for the tracking generator) */ case MSG_TG_LO_DRIVE: if ( dataLen != 0 ) // Drive level specified? { tempValue = atoi ( dataBuff ); // Yes, convert to a number if (( tempValue < MIN_DRIVE ) || ( tempValue > MAX_DRIVE )) Serial.printf ( "Invalid drive specification: %d\n", tempValue ); else // Set the transmitter drive level SetTGLoDrive ( tempValue ); // "SetTGLoDrive" saves the settings } Serial.printf ( "Tracking Generator LO Drive: %d (+%udBm)\n", trackGenSetting.LO_Drive, driveLevel[trackGenSetting.LO_Drive] ); //pushSettings (); return true; #endif #ifdef TG_IF_INSTALLED /* * The "TGRXDRIVE" command sets the receiver (IF) oscillator "drive" level in tracking generator mode. */ case MSG_TG_IF_DRIVE: if ( dataLen != 0 ) // Drive level specified? { tempValue = atoi ( dataBuff ); // Yes, convert to a number if (( tempValue < MIN_DRIVE ) || ( tempValue > MAX_DRIVE )) Serial.printf ( "Invalid drive specification: %d\n", tempValue ); else // Set the transmitter drive level SetTGIfDrive ( tempValue ); // "SetTGLoDrive" saves the settings } Serial.printf ( "Tracking Generator IF Drive: %d (+%udBm)\n", trackGenSetting.IF_Drive, driveLevel[trackGenSetting.IF_Drive] ); //pushSettings (); return true; #endif case MSG_SAVE: // Save the scan configuration if ( dataLen != 0 ) // Location specified? { Save ( atoi ( dataBuff )); // Save the settings return true; } else { Serial.println ( "No save location specified!" ); return false; } case MSG_RECALL: // Retrieve saved scan configuration if ( dataLen != 0 ) // Location specified? { Recall ( atoi ( dataBuff )); // Save the settings return true; } else { Serial.println ( "No save location specified!" ); return false; } /* * "FREQ" - Get or set the frequency for selected VFO */ case MSG_VFO_FREQ: if ( dataLen != 0 ) // Frequency specified? { freq1 = ParseFrequency ( dataBuff ); // Yes, get new frequency if ( VFO == RX_4432 ) // Receiver? rcvr.SetFrequency ( freq1 ); if ( VFO == TX_4432 ) // Transmitter? xmit.SetFrequency ( freq1 ); #ifdef TG_IF_INSTALLED if ( VFO == TGIF_4432 ) // Track generator IF? tg_if.SetFrequency ( freq1 ); #endif #ifdef TG_LO_INSTALLED if ( VFO == TGLO_4432 ) // Track generator IF? tg_lo.SetFrequency ( freq1 ); #endif } if ( VFO == RX_4432 ) // Receiver? Serial.printf ( "RX frequency: %s\n", FormatFrequency ( rcvr.GetFrequency() )); if ( VFO == TX_4432 ) // Local Oscillator? Serial.printf( "LO frequency: %s\n", FormatFrequency ( xmit.GetFrequency() )); #ifdef TG_IF_INSTALLED if ( VFO == TGIF_4432 ) // Local Oscillator? Serial.printf( "TG IF frequency: %s\n", FormatFrequency ( tg_if.GetFrequency() )); #endif #ifdef TG_LO_INSTALLED if ( VFO == TGLO_4432 ) // Local Oscillator? Serial.printf( "TG LO frequency: %s\n", FormatFrequency ( tg_lo.GetFrequency() )); #endif return true; /* * The "ATTEN" command sets the PE4302 attenuation */ case MSG_ATTEN: // Set attenuation if ( dataLen != 0 ) // Anything entered? { attenuation = atoi ( dataBuff ); // Get the value if (( attenuation < 0 ) || ( attenuation > PE4302_MAX )) // Range check { Serial.printf ( "Illegal attenuator setting: %ddB\n", attenuation ); return false; } SetAttenuation ( attenuation ); } Serial.printf ( "Attenuator setting: %ddB\n", setting.Attenuate ); pushSettings (); return true; case MSG_HELP: // "HELP" or "?" ShowMenu (); return true; case MSG_STEPS: // Set number of scan points if ( dataLen != 0 ) // Anything entered? steps = atoi ( &dataBuff[dataIx] ); Serial.print ( "Steps: " ); Serial.println ( steps ); return true; /* * The "DELAY" command sets the timestep in uS. */ case MSG_DELAY: // Set the timestep in uS, currently 2000 if ( dataLen != 0 ) // Anything entered? { delaytime = atol ( dataBuff ); // Yes, set new delay time DisplayInfo (); } Serial.printf ( "Time per scan step = %uuS\n", delaytime ); return true; /* * The "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 */ case MSG_VFO: if ( dataLen != 0 ) // Anything entered? { if ( dataBuff[0] == 'R' ) // Receiver? VFO = RX_4432; else if (( dataBuff[0] == 'L' ) || ( dataBuff[0] == 'T' )) // Transmitter? VFO = TX_4432; #ifdef TG_IF_INSTALLED else if ( dataBuff[0] == 'I' ) // track gen IF VFO = TGIF_4432; #endif #ifdef TG_LO_INSTALLED else if ( dataBuff[0] == 'G' ) // track gen LO VFO = TGLO_4432; #endif else // Illegal entry { VFO = RX_4432; // No, default to receiver Serial.println ( "Illegal VFO specification! "); } } Serial.print ( "Selected VFO is: " ); // Confirm setting switch ( VFO ) { case RX_4432: // Receiver Serial.printf ( " Receiver (%u)\n", VFO ); break; case TX_4432: // Transmitter (LO) Serial.printf ( " Local Oscillator (%u)\n", VFO ); break; case TGIF_4432: // Transmitter (LO) Serial.printf ( " Tracking Generator IF (%u)\n", VFO ); break; case TGLO_4432: // Transmitter (LO) Serial.printf ( " Tracking Generator LO (%u)\n", VFO ); break; } return true; /* * The "RBW" command sets the resolution bandwidth (RBW). * * Entering "RBW A" (or "RBW AUTO") puts the RBW into automatic mode. */ case MSG_RBW: if ( dataLen != 0 ) // Anything entered? { if ( dataBuff[0] == 'A' ) // Auto RBW requested? bandwidth = 0; // Set bandwidth to '0' else if ( isDigit ( dataBuff[0] )) // Was a number entered? { bandwidth = atof ( dataBuff ); // Yes, then get entered value if ( bandwidth < 2.7 ) bandwidth = 2.7; } // Serial.print ( "RBW: " ); Serial.println ( bandwidth ); // setting.Bandwidth10 = (int) ( bandwidth * 10 ); // Here also SetRBW ( (int) ( bandwidth * 10 )); } Serial.print ( "RBW = " ); if ( setting.Bandwidth10 == 0 ) Serial.printf ( "AUTO (%.1fHKz)\n", bandwidth ); else Serial.printf ( "%.1fHKz\n", bandwidth ); return true; // End of 'W' command case MSG_TRACES: // Only works on 'GAIN' trace for now if ( dataLen != 0 ) // Anything entered? { if ( dataBuff[0] == 'G' ) // Gain trace requested? { setting.ShowGain = !setting.ShowGain; // Toggle the setting WriteSettings (); // Save setting return true; // Processed successfully } if ( dataBuff[0] == 'D' ); // Manin scan requested? { setting.ShowSweep = !setting.ShowSweep; // Toggle the setting WriteSettings (); // Save setting return true; // Processed successfully } } else // Nothing specified { Serial.println ( "No 'TRACES' item specified!" ); return false; // Unsuccessful } case MSG_IF_FREQ: if ( dataLen != 0 ) // Anything entered? { if ( dataBuff[0] == '+' ) // User wants to add an increment? { strcpy ( tempBuff, &dataBuff[1] ); // Copy the number to "tempBuff" tempValue = 1; // We'll multiply by '+1' } else if ( dataBuff[0] == '-' ) // User wants to add a decrement? { strcpy ( tempBuff, &dataBuff[1] ); // Copy the number to "tempBuff" tempValue = -1; // We'll multiply by '1' } else // No sign entered { strcpy ( tempBuff, dataBuff ); // Copy the number to "tempBuff" tempValue = 0; // Indicate full frequency in the buffer } tempFreq = ParseFrequency ( tempBuff ); // Convert frequency string to a number if ( tempValue != 0 ) // Increment or decrement? tempFreq = ( tempFreq * tempValue ) + setting.IF_Freq; if ( !SetIFFrequency ( tempFreq )) // Returns result of range check Serial.println ( "Requested IF frequency exceeds limits; not changed!" ); } Serial.printf ( "IF Frequency set to: %s\n", FormatFrequency ( setting.IF_Freq )); return true; /* * "REGDUMP" Print the registers for selected VFO */ case MSG_REG_DUMP: if ( VFO == RX_4432 ) // Receiver? { Serial.println ( "\nReceiver registers:\n" ); rcvr.PrintRegs(); } if ( VFO == TX_4432 ) // Transmitter? { Serial.println ( "\nTransmitter (LO) registers:\n" ); xmit.PrintRegs(); } #ifdef TG_IF_INSTALLED if ( VFO == TGIF_4432 ) // Track generator IF { Serial.println ( "\nTracking Generator IF registers:\n" ); tg_if.PrintRegs(); } #endif #ifdef TG_LO_INSTALLED if ( VFO == TGLO_4432 ) // Track generator Lo { Serial.println ( "\nTracking Generator LO registers:\n" ); tg_lo.PrintRegs(); } #endif return true; /* * The "RSSI" and "QUIT" commands turn the displaying of the RSSI (Receiver Signal * Strength Indicator) readings in the scan. * * Notice "showRSSI" gets set to '2'. I tried making it an option to only show one * pass through the sweep, but somehow, the output starts mid-sweep. Until I figure * that out, continuois is the onlt option. The hooks for that are still in the * sweep loop. */ case MSG_RSSI: if ( dataLen != 0 ) // Anything entered? { if ( dataBuff[0] == 'C' ) // Continuous reporting requested? showRSSI = 2; // Yes else if ( dataBuff[0] == '1' ) // Or just one time? showRSSI = 1; // Set it } else // No specification showRSSI = 1; // Then the default is one time initSweep = true; // Restart the sweep return true; case MSG_QUIT: showRSSI = 0; // Turn RSSI display off return true; /* * The "REGISTER" command can read or write any of the Si4432 registers; it's pretty clever! * * The register designation and (optional) data to be written are in HEX. If you enter * something like "REGISTER 2F", the response will be the contents of the specified register * (in this case, "2F"). * * If you enter something like "REGISTER 69 2", it will write 0x02 into register 0x69. * * Note, the current setting of "VFO" ("VFO" command) must be either the transmitter * or receiver modules. */ case MSG_SET_REG: tempBuff[0] = 0; // Start with null terminator tempIx = 0; // And zero out the index; if (( VFO != RX_4432 ) && ( VFO != TX_4432 ) && ( VFO != TGIF_4432 ) && ( VFO != TGLO_4432 ) ) { Serial.print ( "Illegal Si4432 module specified; 'VFO' = " ); Serial.println ( VFO ); return false; } if ( dataLen == 0 ) // Any data? { Serial.println ( "No register specified!" ); return false; } /* * Ok, there is some data there so let's extract the register specification. */ while ( dataIx < dataLen ) { c = dataBuff[dataIx++]; // Next character from input if ((( c != ' ' ) && ( c != ',' )) && ( isHex ( c ))) { tempBuff[tempIx++] = c; // Copy to temporary buffer tempBuff[tempIx] = 0; // Add null terminator } else if (( c != ' ' ) || ( c != ',' )) // If it's a space or a comma break; // On to the next step if ( !isHex ( c )) // If not a valid hex digit { Serial.printf ( "'%c' is not a hexadecimal digit!\n", c ); return false; } } addr = xtoi ( tempBuff ) & 0xFF; // Convert buffer to address tempIx = 0; // Start over for data if any tempBuff[0] = 0; // Null terminate the buffer /* * If we got this far, we have a register address. Now we need to determine whether * or not the user also specified a value to set in that register or if they just * want a readout of what's in the register. */ while ( dataIx < dataLen ) { c = dataBuff[dataIx++]; // Next character from input if ( !isHex ( c )) // Check for non hexadecimal digit { Serial.printf ( "'%c' is not a hexadecimal digit!\n", c ); return false; } tempBuff[tempIx++] = c; // Copy to other buffer tempBuff[tempIx] = 0; // Null terminator } if ( strlen ( tempBuff ) > 0 ) // If something in the buffer { tempValue = xtoi ( tempBuff ) & 0xFF; // Convert it to a hex number if ( VFO == RX_4432 ) // Receiver? rcvr.WriteByte ( addr, tempValue ); // Send data to the device if ( VFO == TX_4432 ) // Transmitter? xmit.WriteByte ( addr, tempValue ); // Send data to the device #ifdef TG_IF_INSTALLED if ( VFO == TGIF_4432 ) // Track gen IF tg_if.WriteByte ( addr, tempValue ); // Send data to the device #endif #ifdef TG_LO_INSTALLED if ( VFO == TGLO_4432 ) // Track gen LO tg_lo.WriteByte ( addr, tempValue ); // Send data to the device #endif } /* * So far, so good! When we get here, either the user just wanted to see what's in * the register or we have already changed it. Now just report the contents of the * selected register. */ if ( VFO == RX_4432 ) // Receiver? Serial.printf ( "RX Reg[0x%02X] = 0x%02X\n", addr, rcvr.ReadByte ( addr )); else if ( VFO == TX_4432 ) // Transmitter? Serial.printf ( "TX Reg[0x%02X] = 0x%02X\n", addr, xmit.ReadByte ( addr )); #ifdef TG_IF_INSTALLED else if ( VFO == TGIF_4432 ) // Transmitter? Serial.printf ( "TGIF Reg[0x%02X] = 0x%02X\n", addr, tg_if.ReadByte ( addr )); #endif #ifdef TG_LO_INSTALLED else if ( VFO == TGLO_4432 ) // Transmitter? Serial.printf ( "TGLO Reg[0x%02X] = 0x%02X\n", addr, tg_lo.ReadByte ( addr )); #endif return true; /* * The "TUNE" comand allows the use to specify the crystal load capacitance for the * selected VFO. This teaks the frequency. See A440 and the TinySA documentation for * info on how it really works. * * We need to force the SI4432 to recalibrate the PLL after each tune by putting it into READY mode * */ case MSG_TUNE: if ( dataLen != 0 ) // Value specified? { tempValue = xtoi ( dataBuff ); // Yes, get new frequency (in hex) if ( VFO == RX_4432 ) // Receiver? { rcvr.Tune ( tempValue ); // Tune it config.RX_capacitance = tempValue; // And save it } if ( VFO == TX_4432 ) // Transmitter? { xmit.Tune ( tempValue ); // Tune it config.TX_capacitance = tempValue; // And save it } #ifdef TG_IF_INSTALLED if ( VFO == TGIF_4432 ) // Trackgen IF { tg_if.Tune ( tempValue ); // Tune it config.tgIF_capacitance = tempValue; // And save it } #endif #ifdef TG_LO_INSTALLED if ( VFO == TGLO_4432 ) // Track Gen LO { tg_lo.Tune ( tempValue ); // Tune it config.tgLO_capacitance = tempValue; // And save it } #endif } if ( VFO == RX_4432 ) // Receiver? { tempValue = rcvr.ReadByte ( REG_COLC ); // Get current setting Serial.printf ( "RX capacitance: 0x%2X\n", tempValue ); } if ( VFO == TX_4432 ) // Local Oscillator? { tempValue = xmit.ReadByte ( REG_COLC ); // Get current setting Serial.printf ( "TX capacitance: 0x%2X\n", tempValue ); } #ifdef TG_IF_INSTALLED if ( VFO == TGIF_4432 ) // Tracking gen IF { tempValue = tg_if.ReadByte ( REG_COLC ); // Get current setting Serial.printf ( "TGIF capacitance: 0x%2X\n", tempValue ); } #endif #ifdef TG_LO_INSTALLED if ( VFO == TGLO_4432 ) // Tracking gen LO { tempValue = tg_lo.ReadByte ( REG_COLC ); // Get current setting Serial.printf ( "TGLO capacitance: 0x%2X\n", tempValue ); } #endif return true; /* * The "REF_FREQ" ("MSG_GPIO2") command sets or gets the current frequency setting for * the GPIO2 calibration frequency for the transmitter Si4432. * * The legal values are: * * 30MHz, 15MHz, 10MHz, 4MHz, 3MHz, 2MHz, 1MHz * * WA2FZW - For whatever reason, when a value is entered and the register is set, the * readback of register "REG_MCOC" is correct, but when no data is entered the readback * is always zero. Needs further investigation! */ case MSG_GPIO2: if ( dataLen != 0 ) // Value specified? { if ( dataBuff[0] == 'O' ) // Turn it off? tempIx = 7; // This will do it! else { tempValue = atoi ( dataBuff ); // Yes, get new frequency for ( tempIx = 0; tempIx < 7; tempIx++ ) // Loop through the table if ( tempValue == fTranslate[tempIx] ) // A match? break; // Get out of the loop if ( tempIx > 6 ) // Illegal entry { Serial.printf ( "'%u' is an illegal GPIO2 frequency setting!\n", tempValue ); return false; } } SetRefOutput ( tempIx ); // Set the frequency } tempValue = xmit.ReadByte ( REG_GPIO2 ); // Read the GPIO2 register if ( tempValue == 0x1F ) Serial.println ( "Transmitter GPIO2 reference frequency is off!" ); else { tempValue = xmit.ReadByte ( REG_MCOC ); // Read the clock register Serial.printf ( "Transmitter GPIO2 reference frequency: %uMHz\n", fTranslate[tempValue] ); } return true; case MSG_ACT_PWR: // Calibrate the indicated power level if ( dataLen != 0 ) // Value specified? { tempValue = atof ( dataBuff ); // Yes, get new power reading RequestSetPowerLevel ( tempValue ); Serial.printf ( "Indicated power set to: %f\n", tempValue ); return true; } else { Serial.println ( "No indicated power specified!" ); return false; } case MSG_WIFI_UPDATE: // Set WiFi update target time if ( dataLen != 0 ) // Value specified? { tempValue = atoi ( dataBuff ); // Yes, get new value in milliSeconds // check valid if ( (tempValue > 10) && (tempValue < 2000) ) { wiFiTargetTime = tempValue * 1000; // convert to microSeconds Serial.printf ( "Target wifi Update time set to: %d\n", tempValue ); return true; } else { Serial.println("Invalid - must be between 10ms and 2000ms"); return false; } } else { Serial.printf ( "WiFi update target time is %ums\n", wiFiTargetTime/1000 ); return false; } case MSG_WEBSKT_INTERVAL: // Set WiFi websocket interval time if ( dataLen != 0 ) // Value specified? { tempValue = atoi ( dataBuff ); // Yes, get new value in microSeconds // check valid if ( (tempValue > 50) && (tempValue < 10000) ) { websocketInterval = tempValue; Serial.printf ( "websocket poll interval set to: %uus\n", tempValue ); return true; } else { Serial.println("Invalid - must be between 50us and 10,000us"); return false; } } else { Serial.printf ( "Websocket poll interval is %uus\n", websocketInterval ); return false; } case MSG_WIFI_POINTS: // Set WiFi chunk size if ( dataLen != 0 ) // Value specified? { tempValue = atoi ( dataBuff ); // Yes, get new value // check valid if ( (tempValue > 10) && (tempValue < displayPoints * OVERLAP) ) { wiFiPoints = tempValue; Serial.printf ( "Chunk size set to: %u\n", tempValue ); return true; } else { Serial.printf("Invalid - must be between 10 and %u\n", displayPoints * OVERLAP); return false; } } else { Serial.printf ( "Chunk size is %u\n", wiFiPoints ); return false; } case MSG_CONFIG: // Save configuration settings WriteConfig (); return true; case MSG_PAUSE: // Pause Scan (toggle) paused = ! paused; if (paused) Serial.println ( "Sweep paused" ); else Serial.println ( "Sweep resumed" ); return true; case MSG_SWEEPLO: setMode(SA_LOW_RANGE); return true; case MSG_SIGLO: setMode(SIG_GEN_LOW_RANGE); return true; case MSG_BANDSCOPE: setMode(BANDSCOPE); return true; case MSG_IFSWEEP: setMode(IF_SWEEP); return true; case MSG_RX_SWEEP: setMode(RX_SWEEP); return true; case MSG_OTA: setMode(OTA_UPDATE); return true; case MSG_TGOFFSET: if ( dataLen != 0 ) // Frequency specified? { signedFreq = ParseSignedFrequency ( dataBuff ); // Yes, get new frequency SetTGOffset (signedFreq); } Serial.printf ( "Tracking Generator IF offset: %i\n", trackGenSetting.Offset ); // sort out signed version of format frequency!!!!!!!!!!!!!!!!!!! return true; case MSG_TGFREQ: if ( dataLen != 0 ) // Frequency specified? { freq1 = ParseFrequency ( dataBuff ); // Yes, get new frequency SetTGFreq (freq1); } Serial.printf ( "Tracking Signal Generator frequency: %i\n", FormatFrequency ( trackGenSetting.Frequency )); return true; /* * "FREQ" - Get or set the frequency for IF sweep injected signal */ case MSG_IFSIGNAL: if ( dataLen != 0 ) // Frequency specified? { freq1 = ParseFrequency ( dataBuff ); // Yes, get new frequency SetIFsweepSigFreq (freq1); } Serial.printf ( "IF Sweep Signal frequency: %s\n", FormatFrequency ( GetIFsweepSigFreq() )); return true; case MSG_SPUR: if ( dataLen != 0 ) // Anything entered? { // if ( dataBuff[0] == '1' ) // Turn Spur reduction on if ( strcmp ( dataBuff, "ON" ) == 0 ) { SetSpur (1); return true; // Processed successfully } if ( strcmp ( dataBuff, "OFF" ) == 0 ) // if ( dataBuff[0] == '0' ); // Turn Spur reduction off { SetSpur (0); return true; // Processed successfully } Serial.println( "Invalid argument for Spur command - use 1(on) or 0(off)" ); return false; } else // Nothing specified { Serial.printf ( "Spur reduction: %s\n", setting.Spur ? "ON" : "OFF" ); return false; } 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; /* * Set external gain or attenuation */ case MSG_EXTGAIN: if ( dataLen != 0 ) // Value specified? { tempFloat = atof ( dataBuff ); // Yes SetExtGain( tempFloat ); DisplayInfo (); // Re display the scan } Serial.printf ( "External gain: %4.1f\n", setting.ExternalGain ); return true; /* * Start calibration of the SI4432 bandpass filters (ie different RBW) * Valid only if already in RX Sweep mode */ case MSG_BPFCAL: return StartBpfCal (); default: return false; } // End of "switch" } // End of "ProcessCommand" /* * "ParseFrequency" expects a frequency in the "freqString". The format of the string * is designed to be consistent with the way frequencies are entered using the touch * screen menus. That is one can enter something like "100M" (or "100m") to specify * 100MHz or "100k" (or "100K") to specify 100KHz. if you want to enter the * value in pure Hertz, you can use commas (or dots) for clarity; for example, * "100,000,000" would specify 100MHz. */ uint32_t ParseFrequency ( char* freqString ) { uint32_t freq = 0; // Used to build the frequency uint8_t index = 0; // Input buffer index uint8_t digit; // Next digit for ( index = 0; index < strlen ( freqString ); index++ ) { digit = freqString[index]; // Get a digit or something else if ( isDigit ( digit )) // If it is a number freq = ( freq * 10 ) + ( digit - 0x30 ); // Cheap atoi conversion else if ( digit == 'M' ) { freq *= 1000000; // Make it MHz return freq; // And send it back } else if ( digit == 'K' ) { freq *= 1000; // Make it KHz return freq; // And send it back } } return freq; } int32_t ParseSignedFrequency ( char* freqString ) { int32_t freq = 0; // Used to build the frequency uint8_t index = 0; // Input buffer index uint8_t digit; // Next digit int32_t negative = 1; for ( index = 0; index < strlen ( freqString ); index++ ) { digit = freqString[index]; // Get a digit or something else if ( isDigit ( digit )) // If it is a number freq = ( freq * 10 ) + ( digit - 0x30 ); // Cheap atoi conversion else if ( digit == 'M' ) { freq *= 1000000; // Make it MHz return freq * negative; // And send it back } else if ( digit == 'K' ) { freq *= 1000; // Make it KHz return freq * negative; // And send it back } else if ( digit == '-' ) { negative = -1; } } return freq * negative; } /* * "FormatFrequency" breaks down the frequency into a format of "nnn,nnn,nnn" in the * frequency buffer. */ char* FormatFrequency ( uint32_t freq ) { uint32_t MHz; // MHz part of frequency uint32_t KHz; // KHz part of frequency uint32_t remainder; // Leftover pieces static char freqBuff[20] = {0}; // The formatted frequency MHz = freq / 1000000; // Isolate MHz remainder = freq % 1000000; // The remainder of the frequency KHz = remainder / 1000; // Isolate KHz remainder = remainder % 1000; // Low order 3 digits if ( freq >= 1000000 ) sprintf ( freqBuff, "%lu,%03lu,%03lu Hz", MHz, KHz, remainder ); else if ( freq >= 1000 ) sprintf ( freqBuff, "%lu,%03lu Hz", KHz, remainder ); else sprintf ( freqBuff, "%lu Hz", remainder ); return freqBuff; } /* * "FormatSignedFrequency" breaks down the frequency into a format of "nnn,nnn,nnn" in the * frequency buffer. */ char* FormatSignedFrequency ( int32_t freq ) { uint32_t uFreq; // turned positive uint32_t MHz; // MHz part of frequency uint32_t KHz; // KHz part of frequency uint32_t remainder; // Leftover pieces static char freqBuff[20] = {0}; // The formatted frequency static char firstChar = ' '; if (freq < 0) uFreq = -freq; else uFreq = freq; if (freq < 0) firstChar = '-'; MHz = uFreq / 1000000; // Isolate MHz remainder = uFreq % 1000000; // The remainder of the frequency KHz = remainder / 1000; // Isolate KHz remainder = remainder % 1000; // Low order 3 digits if ( uFreq >= 1000000 ) sprintf ( freqBuff, "%c%lu,%03lu,%03lu Hz", firstChar, MHz, KHz, remainder ); else if ( uFreq >= 1000 ) sprintf ( freqBuff, "%c%lu,%03lu Hz", firstChar, KHz, remainder ); else sprintf ( freqBuff, "%c%lu Hz", firstChar, remainder ); return freqBuff; } /* * "DisplayFrequency" breaks down the frequency into a format of "nnn,nnn,nnn" in the * frequency buffer, with leading zeros and no unit */ char* DisplayFrequency ( uint32_t freq ) { uint32_t MHz; // MHz part of frequency uint32_t KHz; // KHz part of frequency uint32_t remainder; // Leftover pieces static char freqBuff[20] = {0}; // The formatted frequency MHz = freq / 1000000; // Isolate MHz remainder = freq % 1000000; // The remainder of the frequency KHz = remainder / 1000; // Isolate KHz remainder = remainder % 1000; // Low order 3 digits sprintf ( freqBuff, "%03lu,%03lu,%03lu", MHz, KHz, remainder ); return freqBuff; } /* * "xtoi()" works similar to "atoi()" except the string is assumed to be hex numbers. It will * convert characters in the string as long as the next character in the string is a valid hex * digit. In other words it stops converting if it sees a null or a ';', or any thing else that * is not a valid hex digit. The only exception is the character "X" (or "x"), which is ignored. * This allows it to correctly convert a string in the "0xNN..." format. */ uint16_t xtoi ( char* hexString ) { int ix; // Loop index char c; // Single character uint16_t answer = 0; // The answer for ( ix = 0; ix < strlen ( hexString ); ix++ ) { c = toupper ( hexString[ix] ); // Get next character if ( c == 'X' ) continue; if ( isdigit ( c )) // Normal base 10 digit? answer = ( answer * 16 ) + ( c - '0' ); // shift answer and add new number else if ( c >= 'A' && c <= 'F' ) // Valid hex digit? answer = ( answer * 16 ) + ( c - 'A' + 10 ); // Shift answer and add new number else break; } return answer; } /* * "isHex" returns true if the character is a valid hexadecimal digit or the letter 'X' * (or 'x') as used in formatting a hexadecimal number as "0xNN". */ bool isHex ( char c ) { char myC = toupper ( c ); // Make uppercase if (( myC >= '0' ) && ( myC <= '9' )) // standard digits return true; if (( myC >= 'A' ) && ( myC <= 'F' )) // More digits return true; if ( myC == 'X' ) // Special hex formatting return true; return false; // If none of the above } /* * "UpdateMarker" changes the enabled/disabled status of a marker or its color. * The "mkr" argument is the index to the marker, not its number. * * The "action" argument is (for now) a single character from the following list: * * [E]NABLE Turn the marker on * [D]ISABLE Turn the marker off * [W]HITE Set the color to white * [R]ED Set the color to red * [B]LUE etc. * [G]REEN * [Y]ELLOW * [O]RANGE */ bool UpdateMarker ( uint8_t mkr, char action ) { uint8_t oldMkrStatus; oldMkrStatus = marker[mkr].Status (); // Remember marker'scurrent status switch ( action ) // What are we supposed to change { case 'E': // Enable the marker marker[mkr].Enable (); break; // Return success case 'D': // Disable the marker marker[mkr].Disable (); break; // Return success case 'W': // Set color to white marker[mkr].Color ( WHITE ); break; // Return success case 'R': // Set color to red marker[mkr].Color ( RED ); break; // Return success case 'B': // Set color to blue marker[mkr].Color ( BLUE ); break; // Return success case 'G': // Set color to green marker[mkr].Color ( GREEN ); break; // Return success case 'Y': // Set color to yellow marker[mkr].Color ( YELLOW ); break; // Return success case 'O': // Set color to orange marker[mkr].Color ( ORANGE ); break; // Return success default: // If none of the above return false; // Illegal action/color } /* * If we get here, the marker number and action were good. Now see if something * actually changes and if so, update and save the "setting" structure. */ if ( oldMkrStatus != marker[mkr].Status () ) { setting.MkrStatus[mkr] = marker[mkr].Status (); WriteSettings (); } return true; // Marker command processed ok } /* * "SetPreampGain" sets the preamp gain, which was added to the "setting" structure in * Version 2.5. "GetPreampGain" reads it from the Si4432 and reports the current setting. */ void SetPreampGain ( uint8_t gain ) { uint8_t oldGain = setting.PreampGain; // Remember current setting // Serial.printf ( "Cmd.cpp - SetPreampGain %i \n", gain ); if (( gain == 0 ) || ( gain == 0x60 )) // AGC on request? gain = 0x60; // That will be the setting else if ( gain < 25 ) // If < 25 use low range { gain = ( gain - 5 ) / 3; // Value for the register gain &= PGAGAIN; // Just the low 4 bits } else if ( gain >= 25 ) // If we found it above { gain = ( gain - 25 ) / 3; // Value for the register gain &= PGAGAIN; // Just the low 4 bits gain |= LNAGAIN; // Plus the high range bit } setting.PreampGain = gain; // Set new value in the "setting" structure rcvr.SetPreampGain ( gain ); // and in the receiver module if ( oldGain != setting.PreampGain ) // Did it actually change? { changedSetting = true; // Yes, need to update the display WriteSettings (); // and save the "setting" structure } } uint8_t GetPreampGain ( bool* agc, uint8_t* reg ) { uint8_t Reg69 = rcvr.GetPreampGain (); // Read the register int lnaGain = 5; // Assume low gain if ( Reg69 & LNAGAIN ) // If LNA is turned on lnaGain = 25; // Add 25 dB to total gain int pgaGain = ( Reg69 & PGAGAIN ) * 3; // 3dB per bit *agc = (bool) ( Reg69 & AGCEN ); // Set true if AGC is enabled *reg = Reg69 & ( PGAGAIN | LNAGAIN ); return lnaGain + pgaGain; // Total gain } /* * "SetRefOutput" - Sets the GPIO2 frequency for the LO Si4432 module */ void SetRefOutput ( int freq ) { int oldFreq = setting.ReferenceOut; setting.ReferenceOut = freq; xmit.SetPowerReference ( freq ); if ( oldFreq != setting.ReferenceOut ) { changedSetting = true; WriteSettings (); } } /* * "SetRefLevel" - Sets the decibel level for the top line of the graph. */ void SetRefLevel ( int ref ) { int oldMin = setting.MinGrid; int oldMax = setting.MaxGrid; setting.MaxGrid = ref; setting.MinGrid = ref - yGrid * setting.PowerGrid; if (( oldMin != setting.MinGrid ) || ( oldMax != setting.MaxGrid )) { changedSetting = true; WriteSettings (); } } /* * "SetGenerate" - Puts the unit into Signal Generator Mode * Will be superceded by changing setting.mode */ void SetGenerate ( int8_t g ) { int oldG = setting.Generate; setting.Generate = g; if (g) { tinySA_mode = SIG_GEN_LOW_RANGE; } else { tinySA_mode = SA_LOW_RANGE; } if ( oldG != setting.Generate ) { changedSetting = true; WriteSettings (); } } /* * "SetTracking" - sets the Tracking Generator Mode * 0 = off, 1 = track, 2 = sig gen */ void SetTracking ( int8_t m ) { int oldM = trackGenSetting.Mode; trackGenSetting.Mode = m; if ( oldM != trackGenSetting.Mode ) { changedSetting = true; WriteTrackGenSettings (); pushSettings (); // Serial.println("SetTracking - pushSettings"); } } /* * "SetPowerGrid" - Sets the dB/vertical divison on the grid */ void SetPowerGrid ( int g ) { int oldGrid = setting.PowerGrid; setting.PowerGrid = g; setting.MinGrid = setting.MaxGrid - yGrid * g; if ( oldGrid != setting.PowerGrid ) { changedSetting = true; WriteSettings (); } } /* * Set the IF frequency - M0WID addition */ bool SetIFFrequency ( int32_t freq ) { int32_t oldFreq = setting.IF_Freq; // Serial.print ( "SetIF: freq = " ); Serial.println ( freq ); if (( freq < MIN_IF_FREQ ) || ( freq > MAX_IF_FREQ )) // If out of limits return false; // Don't change it // Serial.println ( "SetIF: Past range check" ); setting.IF_Freq = freq; if ( oldFreq != setting.IF_Freq ) { changedSetting = true; WriteSettings (); } return true; // Frequency was good } /* * "SetAttenuation" - Modified by WA2FZW: * * "setting.Attenuate" is now stored as a positive number as displaying * an attenuation of "-nn" (as was the case in the original software) * would imply a gain value. All other places where it is used have been * modified accordingly. * * Note, we don't actually set the attenuation in the PE4302 as that is done at * the start of each sweep cycle. */ void SetAttenuation ( int a ) { int oldAtten = setting.Attenuate; setting.Attenuate = a; // Put value in the setting structure if ( oldAtten != setting.Attenuate ) { changedSetting = true; WriteSettings (); } } /* * Set the external gain value * +ve for gain, -ve for attenuation */ void SetExtGain ( double a ) { setting.ExternalGain = a; changedSetting = true; WriteSettings (); pushSettings(); } double GetExtGain (void ) { return setting.ExternalGain; } /* * "SetStorage" - Saves the results of a scan for comparison to an active one. */ void SetStorage ( void ) { for ( int i = 0; i < displayPoints; i++ ) myStorage[i] = myData[i]; setting.ShowStorage = true; } /* * "SetClearStorage" - Logically clears the saved scan data */ void SetClearStorage ( void ) { setting.ShowStorage = false; setting.SubtractStorage = false; } /* * "SetSubtractStorage" */ void SetSubtractStorage ( void ) { if ( !setting.ShowStorage ) SetStorage (); setting.SubtractStorage = true; } /* * "RequestSetPowerLevel" will be called from the menu or WiFi, possibly Serial to request * the offset is calculated so that peak power measured in the sweep matches the input value * the Request function sets the flag to initiate the calibration at the start of the next sweep * using the input value. */ void RequestSetPowerLevel ( float o ) { actualPower = o; setActualPowerRequested = true; // Serial.printf ( "Request set Power Level %f \n", o ); } /* * "SetPowerLevel" * Adjusts displayed power to match the actual input signal power - calibration function */ void SetPowerLevel ( double o ) { double oldOffset = setting.LevelOffset; if ( o != 100.0 ) setting.LevelOffset = ( o - ( (double)oldPeakLevel / 2.0 + setting.Attenuate - setting.ExternalGain - 120.0) ); // WA2FZW else setting.LevelOffset = 0.0; // Serial.printf ( "Peak level: %i, Actual: %f, Level offset: %f \n", // oldPeakLevel, o, setting.LevelOffset ); if ( oldOffset != setting.LevelOffset ) { changedSetting = true; RedrawHisto (); // Redraw labels and restart sweep with new settings WriteSettings (); pushSettings(); // Serial.println("Power level changed"); } } /* * "SetRBW" - Sets the resolution bandwidth in the "setting" structure. We don't * bother to actually set it in the receiver Si4432 as that is done at the start * of each scan cycle. */ void SetRBW ( int bw ) { int16_t oldRBW = setting.Bandwidth10; int16_t tempBw; if ( bw == 0 ) // "AUTO" mode requested tempBw = ( setting.ScanStop - setting.ScanStart ) / 30000; else tempBw = bw; setting.Bandwidth10 = bw; // bandwidth = rcvr.SetRBW ( tempBw, &delaytime ); // Not needed as will be done at start of sweep RedrawHisto (); // Redraw labels and restart sweep with new settings if ( setting.Bandwidth10 != oldRBW ) { changedSetting = true; WriteSettings (); } } /* * "SetSpur" - Turns spurious signal supression on or off * Also turns on the MIN trace which is needed for the spur reduction to be seen. * Only turns the average trace off of it is still set to min * Also turns off/on the dB trace */ void SetSpur ( int v ) { int oldSpur = setting.Spur; setting.Spur = v; if (setting.Spur) { setting.Average = AV_MIN; setting.ShowSweep = false; } else { if (setting.Average == AV_MIN) setting.Average = AV_OFF; setting.ShowSweep = true; } if ( oldSpur != setting.Spur ) { changedSetting = true; WriteSettings (); pushSettings (); } } /* * "SetAverage" - Sets the number of readings to average for each display point */ void SetAverage ( int v ) { int oldAvg = setting.Average; setting.Average = v; if ( oldAvg != setting.Average ) { changedSetting = true; WriteSettings (); } } /* * "SetLoDrive" sets the transmitter output level. * * The drive value can be from '0' to '7' and the output power will be set * according to the following table (from page 39 of the Si4432 datasheet): * * 0 => +1dBm 4 => +11dBm * 1 => +2dBm 5 => +14dBm * 2 => +5dBm 6 => +17dBm * 3 => +8dBm 7 => +20dBm */ void SetLoDrive ( uint8_t level ) { int oldLevel = setting.Drive; if (( level < 0 ) || ( level > 7 )) return; else { setting.Drive = level; xmit.SetDrive ( level ); // Set transmitter power level if ( oldLevel != setting.Drive ) { changedSetting = true; WriteSettings (); pushSettings (); } } } /* Set SGFreq sets the signal generator Frequency * Signal generator frequency may change frequently, so don't save the setting * Settings are saved when leaving the mode * parameter is in Hz */ bool SetSGFreq (uint32_t f) { if ( ( f >= MIN_SIGLO_FREQ ) && ( f <= MAX_SIGLO_FREQ ) ) { sigGenSetting.Frequency = f; return true; } else return false; } /* * Sig gen state is not saved - always want to start in off state */ void SetSGState (uint16_t s) { sigGenOutputOn = s; } /* * "SetSGLoDrive" sets the transmitter max output level in signal generator mode. * * The drive value can be from '0' to '7' and the output power will be set * according to the following table (from page 39 of the Si4432 datasheet): * * 0 => +1dBm 4 => +11dBm * 1 => +2dBm 5 => +14dBm * 2 => +5dBm 6 => +17dBm * 3 => +8dBm 7 => +20dBm */ void SetSGLoDrive ( uint8_t level ) { int oldLevel = sigGenSetting.LO_Drive; if (( level < 0 ) || ( level > 7 )) return; else { sigGenSetting.LO_Drive = level; if (setting.Mode == SIG_GEN_LOW_RANGE) xmit.SetDrive ( level ); // Set transmitter power level if ( oldLevel != sigGenSetting.LO_Drive ) { changedSetting = true; WriteSigGenSettings (); } } } /* * "SetSGRxDrive" sets the receiver output level in signal generator mode * * The drive value can be from '0' to '7' and the output power will be set * according to the following table (from page 39 of the Si4432 datasheet): * * 0 => +1dBm 4 => +11dBm * 1 => +2dBm 5 => +14dBm * 2 => +5dBm 6 => +17dBm * 3 => +8dBm 7 => +20dBm * Note the power limits of the B3555 SAW filter is 10dBm * RX_SI4432_MAX_DRIVE is set in my_SA.h to suit the attenuator pad and losses * in your build. */ void SetSGRxDrive ( uint8_t level ) { int oldLevel = sigGenSetting.RX_Drive; if (( level < 0 ) || ( level > RX_SI4432_MAX_DRIVE )) return; else { sigGenSetting.RX_Drive = level; if (setting.Mode == SIG_GEN_LOW_RANGE) rcvr.SetDrive ( level ); // Set transmitter power level if ( oldLevel != sigGenSetting.RX_Drive ) { changedSetting = true; WriteSigGenSettings (); } } } void SetSGPower ( int16_t dBm ) { // Add algorithm to set attenuator and IF drive level // initially limit to just set attenuator if ( dBm > sigGenSetting.Calibration ) dBm = sigGenSetting.Calibration; if ( dBm < (sigGenSetting.Calibration - ATTENUATOR_RANGE - RX_SI4432_MAX_DRIVE * 3) ) dBm = sigGenSetting.Calibration - ATTENUATOR_RANGE; sigGenSetting.Power = dBm; changedSetting = true; } #ifdef TG_LO_INSTALLED /* * "SetTGLoDrive" sets the transmitter max output level for the tracking generator. * * The drive value can be from '0' to '7' and the output power will be set * according to the following table (from page 39 of the Si4432 datasheet): * * 0 => +1dBm 4 => +11dBm * 1 => +2dBm 5 => +14dBm * 2 => +5dBm 6 => +17dBm * 3 => +8dBm 7 => +20dBm */ void SetTGLoDrive ( uint8_t level ) { int oldLevel = trackGenSetting.LO_Drive; if (( level < 0 ) || ( level > 7 )) return; else { trackGenSetting.LO_Drive = level; tg_lo.SetDrive ( level ); // Set transmitter power level if ( oldLevel != trackGenSetting.LO_Drive ) { changedSetting = true; WriteTrackGenSettings (); } } } #endif #ifdef TG_IF_INSTALLED /* * "SetTGIfDrive" sets the transmitter output level for the tracking generator IF. * * The drive value can be from '0' to '7' and the output power will be set * according to the following table (from page 39 of the Si4432 datasheet): * * 0 => +1dBm 4 => +11dBm * 1 => +2dBm 5 => +14dBm * 2 => +5dBm 6 => +17dBm * 3 => +8dBm 7 => +20dBm */ void SetTGIfDrive ( uint8_t level ) { int oldLevel = trackGenSetting.IF_Drive; if (( level < 0 ) || ( level > 7 )) return; else { trackGenSetting.IF_Drive = level; tg_if.SetDrive ( level ); // Set transmitter power level if ( oldLevel != trackGenSetting.IF_Drive ) { changedSetting = true; WriteTrackGenSettings (); pushSettings (); } } } #endif #ifdef TG_IF_INSTALLED void SetTGPower ( int16_t dBm ) { // Add algorithm to set attenuator and IF drive level // initially limit to just set drive levels // TG Output = IF output - mixer loss - attenuator pads int16_t pathLoss = 10; //6.9 mixer + 3 pad; int16_t maxOut = 20 - pathLoss; int16_t minOut = 1 - pathLoss; if (dBm > maxOut) dBm = maxOut; if (dBm < minOut) dBm = minOut; uint8_t level = ( dBm + pathLoss + 1 ) / 3; Serial.printf("Set TG power - req dBm %i - level set to %i\n", dBm, level); trackGenSetting.Power = dBm; // saved in SetTGIFDrive SetTGIfDrive ( level ); } #endif /* * Set Tracking generator IF offset from main SA IF * Ideally TG IF should be outside bandwidth of main RX */ bool SetTGOffset ( int32_t offset) { // check valid if ( (offset > -1000000 ) && (offset < 1000000 ) ) trackGenSetting.Offset = offset; changedSetting = true; WriteTrackGenSettings (); } /* * Get Tracking generator IF offset from main SA IF */ int32_t GetTGOffset ( ) { return trackGenSetting.Offset; } /* * Set the Track Gen frequency - M0WID addition */ bool SetTGFreq ( int32_t freq ) { int32_t oldFreq = trackGenSetting.Frequency; if (( freq < MIN_SIGLO_FREQ ) || ( freq > MAX_SIGLO_FREQ )) // If out of limits return false; // Don't change it trackGenSetting.Frequency = freq; if ( oldFreq != trackGenSetting.Frequency ) { changedSetting = true; WriteTrackGenSettings (); } return true; // Frequency was good } /* * "SetSweepStart" and "GetSweepStart" were added in Version 2.3. They used to be in * "switch" statements in "SetSweepFrequency" and "GetSweepFrequency". By making them * separate function, they can be used by the "SetSweepCenter", "SetSweepSpan", etc. * functions, thus eliminating a bunch of redundent code. */ void SetSweepStart ( uint32_t startFreq ) { uint32_t lastStart; // Old ssweep start frequency uint32_t lastStop; // and old stop frequency // Serial.print ( "Requested start = " ); Serial.println ( FormatFrequency ( startFreq )); lastStart = setting.ScanStart; // Save the current start frequency lastStop = setting.ScanStop; // and the current stop frequency if ( startFreq < START_MIN ) // Range check startFreq = START_MIN; if ( startFreq > STOP_MAX ) // Range check startFreq = STOP_MAX; if ( startFreq > setting.ScanStop ) // Start can't be more than current stop setting.ScanStop = STOP_MAX; // So set stop at maximum setting.ScanStart = startFreq; // Set new start frequency if (( setting.ScanStart != lastStart ) || ( setting.ScanStop != lastStop )) // Did it change? { changedSetting = true; // Yes it did RedrawHisto (); // Redraw labels and restart sweep with new settings WriteSettings (); // and save the setting structure } } uint32_t GetSweepStart ( void ) { return setting.ScanStart; } /* * "SetSweepStop" and "GetSweepStop" were also added in Version 2.3. */ void SetSweepStop ( uint32_t stopFreq ) { uint32_t lastStart; // Old ssweep start frequency uint32_t lastStop; // and old stop frequency // Serial.print ( "Requested stop = " ); Serial.println ( FormatFrequency ( stopFreq )); lastStart = setting.ScanStart; // Save the current start frequency lastStop = setting.ScanStop; // and the current stop frequency if ( stopFreq < START_MIN ) // Range check stopFreq = START_MIN; if ( stopFreq > STOP_MAX ) // Range check stopFreq = STOP_MAX; if ( stopFreq < setting.ScanStart ) // Stop can't be less than current start setting.ScanStart = START_MIN; // so set start to minimum frequency setting.ScanStop = stopFreq; // Set new stop frequency if (( setting.ScanStart != lastStart ) || ( setting.ScanStop != lastStop )) // Did it change { changedSetting = true; // Yes it did RedrawHisto (); // Redraw labels and restart sweep with new settings WriteSettings (); // and save the setting structure } } uint32_t GetSweepStop ( void ) { return setting.ScanStop; } /* * Specific start/stop frequency setting for IF Sweep mode */ void SetIFsweepStart ( uint32_t startFreq ) { uint32_t lastStart; // Old sweep start frequency uint32_t lastStop; // and old stop frequency // Serial.print ( "Requested start = " ); Serial.println ( FormatFrequency ( startFreq )); lastStart = startFreq_IF; // Save the current start frequency lastStop = stopFreq_IF; // and the current stop frequency if ( startFreq < IF_START_MIN ) // Range check startFreq = IF_START_MIN; if ( startFreq > IF_STOP_MAX ) // Range check startFreq = IF_STOP_MAX; if ( startFreq > stopFreq_IF ) // Start can't be more than current stop stopFreq_IF = IF_STOP_MAX; // So set stop at maximum startFreq_IF = startFreq; // Set new start frequency if (( startFreq_IF != lastStart ) || ( stopFreq_IF != lastStop )) // Did it change? { changedSetting = true; // Yes it did RedrawHisto (); // Redraw labels and restart sweep with new settings // WriteSettings (); // and save the setting structure } } uint32_t GetIFsweepStart ( void ) { return startFreq_IF; } void SetIFsweepStop ( uint32_t stopFreq ) { uint32_t lastStart; // Old sweep start frequency uint32_t lastStop; // and old stop frequency // Serial.print ( "Requested stop = " ); Serial.println ( FormatFrequency ( stopFreq )); lastStart = startFreq_IF; // Save the current start frequency lastStop = stopFreq_IF; // and the current stop frequency if ( stopFreq < IF_START_MIN ) // Range check stopFreq = IF_START_MIN; if ( stopFreq > IF_STOP_MAX ) // Range check stopFreq = IF_STOP_MAX; if ( stopFreq < startFreq_IF ) // Stop can't be less than current start startFreq_IF = IF_START_MIN; // so set start to minimum frequency stopFreq_IF = stopFreq; // Set new stop frequency if (( startFreq_IF != lastStart ) || ( stopFreq_IF != lastStop )) // Did it change? { changedSetting = true; // Yes it did RedrawHisto (); // Redraw labels and restart sweep with new settings // WriteSettings (); // and save the setting structure } } uint32_t GetIFsweepStop ( void ) { return stopFreq_IF; } void SetIFsweepSigFreq ( uint32_t sigFreq ) { uint32_t lastSigFreq; // Serial.print ( "Requested if sweep signal = " ); Serial.println ( FormatFrequency ( sigFreq_IF )); lastSigFreq = sigFreq_IF; // check in a sensible range if ( ( sigFreq < 1000000 ) || (sigFreq > 100000000 ) ) Serial.println(" Out of range - must be between 1,000,000 and 100,000,000Hz"); else { sigFreq_IF = sigFreq; if ( sigFreq_IF != lastSigFreq ) changedSetting = true; } RedrawHisto (); // Redraw labels and restart sweep with new settings } uint32_t GetIFsweepSigFreq ( void ) { return sigFreq_IF; } /* * Specific 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; } /* * start the bandpass filter calibration sequence * must be in RX_Sweep mode */ boolean StartBpfCal ( void ) { if ( setting.Mode == RX_SWEEP ) { bpfCalibrate = true; Serial.println("Starting bpf calibration"); return true; } else { Serial.println("bpfCal only works in RX_SWEEP (RBW test) mode"); return false; } } /* * Specific start/stop frequency setting for Bandscope mode */ void SetBandscopeStart ( uint32_t startFreq ) { uint32_t lastStart; // Old sweep start frequency // Serial.print ( "Requested start = " ); Serial.println ( FormatFrequency ( startFreq )); lastStart = setting.BandscopeStart; // Save the current start frequency if ( startFreq < START_MIN ) // Range check startFreq = START_MIN; if ( startFreq > STOP_MAX ) // Range check startFreq = STOP_MAX; setting.BandscopeStart = startFreq; // Set new start frequency if ( setting.BandscopeStart != lastStart ) { changedSetting = true; // Yes it did initSweep = true; 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, &bpfIndex ); if ( tempBw != oldRBW ) { changedSetting = true; WriteSettings (); } } uint32_t GetBandscopeStart ( void ) { return setting.BandscopeStart; } /* * Specific start/stop frequency setting for Bandscope mode */ void SetBandscopeSpan ( uint32_t spanFreq ) { uint32_t lastSpan; // Old sweep span // Serial.print ( "Requested bandscope span = " ); Serial.println ( FormatFrequency ( spanFreq )); lastSpan = setting.BandscopeSpan; // Save the current start frequency if ( spanFreq < 100000 ) // Range check spanFreq = 100000; if ( spanFreq > 350000 ) // Range check spanFreq = 350000; setting.BandscopeSpan = spanFreq; // Set new start frequency if ( setting.BandscopeSpan != lastSpan ) { changedSetting = true; // Yes it did initSweep = true; // RedrawHisto (); // Redraw labels and restart sweep with new settings // WriteSettings (); // and save the setting structure } } uint32_t GetBandscopeSpan ( void ) { return setting.BandscopeSpan; } /* * "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 (); } } /* * When the user sets the "CENTER" frequency, we're also going to set the "SPAN". But, * it's a bit tricky! * * The basic assumption is that we will set the "SPAN" equal to the selected "CENTER" * frequency. But if the "SPAN" limits would cause the "START" frequency to go negative * or cause the "STOP" frequency to exceed "STOP_MAX", the difference between the * "CENTER" frequency and whichever limit is exceeded will become 1/2 of the "SPAN". * * Of course, setting the "CENTER" frequency at either one of the limits is going to * result in a zero span. */ void SetSweepCenter ( uint32_t freq, uint8_t span ) { uint32_t startFreq; // These are used in computing the uint32_t stopFreq; // Scan limits uint32_t halfSpan; // Half of the frequency span uint32_t lastStart; // Old ssweep start frequency uint32_t lastStop; // and old stop frequency // Serial.print ( "Requested center = " ); Serial.println ( FormatFrequency ( freq )); lastStart = setting.ScanStart; // Save the current start frequency lastStop = setting.ScanStop; // and the current stop frequency /* * Range check the requested frequency against the sweep limits and adjust accordingly: */ if ( freq > STOP_MAX ) // Range check requested frequency freq = STOP_MAX; if ( freq < START_MIN ) // Range check freq = START_MIN; /* * Compute new start and stop frequencies based on the requested frequenct and whether * a "WIDE" span or a "NARROW" span was requested. */ if ( span == WIDE ) // Using normal span? halfSpan = freq / 2; // Then halfSpan is half the frequency else // Must be a "NARROW" span halfSpan = freq / FOCUS_FACTOR / 2; // Span is banse on the "FOCUS_FACTOR" startFreq = freq - halfSpan; // New computed start frequency stopFreq = freq + halfSpan; // New computed stop frequency /* * From the Department of Redundency Department: Now we have to range check the computed * start and stop frequencies and if either of those is out of bounds, we need to adjust the * computed span accordingly. We'll maintain the requested center frequency though. */ if ( stopFreq > STOP_MAX ) // Range check the stop frequency { stopFreq = STOP_MAX; // Set the stop frequency at the upper limit halfSpan = stopFreq - freq; // Re-compute half span startFreq = freq - halfSpan; // and recompute start frequency } if ( startFreq < START_MIN ) // Range check the start frequency { startFreq = START_MIN; // Set start at the limit halfSpan = freq - startFreq; // Re-compute half span stopFreq = freq + halfSpan; // and re-compute stop frequency } startFreq = freq - halfSpan; // Re-compute start and stopFreq = freq + halfSpan; // stop frequencies // Serial.print ( "Computed start = " ); Serial.println ( FormatFrequency ( startFreq )); // Serial.print ( "Computed stop = " ); Serial.println ( FormatFrequency ( stopFreq )); // Serial.print ( "Half span = " ); Serial.println ( FormatFrequency ( halfSpan )); setting.ScanStart = startFreq; // Set new start frequency setting.ScanStop = stopFreq; // Set new stop frequency if (( setting.ScanStart != lastStart ) || ( setting.ScanStop != lastStop )) // Did it change { changedSetting = true; // Yes it did RedrawHisto (); // Redraw labels and restart sweep with new settings WriteSettings (); // and save the setting structure } } uint32_t GetSweepCenter () { return ( setting.ScanStart + setting.ScanStop ) / 2; } /* * "SetSweepSpan" will maintain the previous center frequency and attempt to simply * change the range of the scan. If the new limits cause the start frequency to go * or the stop frequency to exceed "STOP_MAX", the range will be adjusted to maintain * an equal range on both sides of the center frequency. */ void SetSweepSpan ( uint32_t spanRange ) { uint32_t startFreq; // These are used in computing the uint32_t stopFreq; // Scan limits uint32_t centerFreq; // Current center frequency uint32_t halfSpan; // Half of the frequency span uint32_t lastStart; // Old ssweep start frequency uint32_t lastStop; // and old stop frequency // Serial.print ( "Requested span = " ); Serial.println ( FormatFrequency ( spanRange )); lastStart = setting.ScanStart; // Save the current start frequency lastStop = setting.ScanStop; // and the current stop frequency halfSpan = spanRange / 2; // Half the requested span centerFreq = GetSweepCenter (); // Current center frequency startFreq = spanRange - halfSpan; // New computed start frequency stopFreq = spanRange + halfSpan; // New computed stop frequency /* * Now we have to range check the computed start and stop frequencies and if either * of those is out of bounds, we will adjust the span accordingly. We'll maintain * the previous center frequency though. */ if ( stopFreq > STOP_MAX ) // Range check the stop frequency { stopFreq = STOP_MAX; // Set the stop frequency at the upper limit halfSpan = stopFreq - centerFreq; // Re-compute half span startFreq = centerFreq - halfSpan; // and recompute start frequency } if ( startFreq < START_MIN ) // Range check the start frequency { startFreq = START_MIN; // Set start at the limit halfSpan = centerFreq - startFreq; // Re-compute half span stopFreq = centerFreq + halfSpan; // and re-compute stop frequency } startFreq = centerFreq - halfSpan; // Re-compute start and stopFreq = centerFreq + halfSpan; // stop frequencies // Serial.print ( "Computed start = " ); Serial.println ( FormatFrequency ( startFreq )); // Serial.print ( "Computed stop = " ); Serial.println ( FormatFrequency ( stopFreq )); // Serial.print ( "Half span = " ); Serial.println ( FormatFrequency ( halfSpan )); setting.ScanStart = startFreq; // Set new start frequency setting.ScanStop = stopFreq; // Set new stop frequency if (( setting.ScanStart != lastStart ) || ( setting.ScanStop != lastStop )) // Did it change { changedSetting = true; // Yes it did RedrawHisto (); // Redraw labels and restart sweep with new settings WriteSettings (); // and save the setting structure } } uint32_t GetSweepSpan () { return setting.ScanStop - setting.ScanStart; } /* * "SetFreq" is used by the serial command handler to set a specific frequency * for one of the Si4432 modules. */ void SetFreq ( int vfo, uint32_t freq ) { if ( vfo == RX_4432 ) // Receiver? rcvr.SetFrequency ( freq ); if ( vfo == TX_4432 ) // Transmitter? xmit.SetFrequency ( freq ); } 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(); }