simpleSA/simpleSA.ino

2084 lines
62 KiB
Arduino
Raw Normal View History

2020-08-16 02:03:43 +08:00
/*
2020-08-16 02:50:26 +08:00
* "simpleSA.ino"
2020-08-16 02:03:43 +08:00
*
* This is the main program file for the "TinySA for ESP32" (spectrum analyzer)
*
2020-08-16 02:03:43 +08:00
*
* Copyright (C) 2020 David Wilde (M0WID), John Price (WA2FZW)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*
* 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.
2020-08-16 02:03:43 +08:00
*
* Modified by John Price (WA2FZW):
*
* Version 1.0:
* Just add comments, try to figure out how it all works and move some stuff
* around for clarity!
*
*
* Version 1.1:
* Added the file "Si4432.h" which contains symbols for all the Si4432
* registers and some other things related to it's operation.
*
* Added the files "PE4302.h" and "PE4302.cpp" which provide a class/object
* implementation of the attenuator handling. One can now use either the
* serial or parallel type modules connected directly to the processor
* or the parallel module interfaced via a PCF8574 GPIO expander chip as
* Glenn (VK3PE) did on his hardware implementation.
*
* The value stored in "setting.attenuate" is now a positive number. Previously
* it was stored as a negative value and displayed as "Atten: -nn", which would
* imply a gain! The PE4302 library also range limits the attenuator setting
* to a value between 0dB and 31dB, which is the range of the PE4302.
*
* Incorporated changes from David's Version 05 release. Those changes include
* eliminating the support for the Arduino and the addition of WiFi capability.
*
* Added the file "Si4432.h" which contains symbols for all the Si4432
*
*
* Version 1.6:
* Added the file "Si4432.cpp" and moved all of the functions to handle the
* interface to the hardware out of here. *
*
* Version 1.7:
* Moved all the global variables to the top of the file and moved the "setup"
* and "loop" functions to the top where they are customarily found.
*
* Stripped the serial interface command handler out of the "loop" function and
* created the "CheckCommand" function; made a lot of changes to how that all
* works also.
*
*
* Version 1.8:
* Replaced the original code to handle the Si4432 modules with a class/object
* implementation using real SPI protocol.
*
*
* Version 2.0:
* Overhauled the serial command handler and help menu.
*
*
* Version 2.1:
* A major overhaul of the entire architecture! I moved all the functions that
* handle reading and processing commands from the serial input into "Cmd.cpp".
* This is the first step into being able to use common command processing for
* the serial interface, web page and touch screen.
*
*
* Version 2.7:
* More restructuring. Added markers . Added more
* commands to the serial command handler. Re-organized the touch screen menus
* in a more logical hierarchy and added some new capabilities there.
*
*
* Version 2.8 Changes by M0WID:
* Data now pushed to the web clients
* Grid y changed to have 10 divisions
* Various bug fixes
* Layout of display changed
* New Signal Generator mode
* Preparation for other modes eg High Frequency Range
* Scale for preamp gain trace added
* Spur Reduction now does something!
*
* Version 3.0a Changes by M0WID
* Variable no of points to push to WiFi clients to give more frequent updates at narrow RBW
* and fewer chart updates at wide RBW to reduce load on slow tablet or smartphone clients
* Only check for websockets at intervals or if client connected as websockets impose a significant time penalty
* Signal Generator menu and keypad frequency entry
* Calibrate of signal generator output added to menu
* Hot spots on touch to get to specific parts of the menu faster
*
* Version 3.0e Changes by M0WID
* IF Sweep implemented to enable characterisation of the SAW filters, with its own menu
* LCD display no longer uses pin2 for reset - it is wired to the ESP32 reset instead, freeing up pin 2 for the tracking generator
* LCD display backlight no longer uses PWM to control the brightness, freeing up Pin 25 for the tracking generator.
* You can uncomment the #define in my_SA.h if you still want the PWM backlight.
* Tracking generator options added, with either one or two additional SI4432. If one SI4432
* then the track generator LO is tapped off the existing LO and the track gen IF runs
* at the same frequency as the receiver. This can give reduced sensitivity.
* If two SI4432 are implemented then the tracking generator IF can be offset to improve rejection
* of the tracking generator IF.
* New console commands for IF Sweep and tracking generator
* New menu for tracking generator accessed from Output Menu
* SPI now runs at 16MHz (was at 1MHz - oops!)
* Additional functions in ui.cmd to allow signed frequency entry.
2020-08-16 02:50:26 +08:00
*
* Version 3.0f Changes by M0WID
*
* Version 0.03
* 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! Version numbering changed - still in beta!
*
* Version 0.1
* Tracking generator now can be used as a signal generator
* Web page for signal generator
* RX sweep for testing the SI4432 FIR filters
* Output range of signal generator increased by also reducing SI4432 output power as well as uing attenuator
* Storage feature - temporary only not to disk - added to web page traces
* X grid lines added to web chart
2020-08-16 02:03:43 +08:00
*/
2020-08-16 02:50:26 +08:00
#include "simpleSA.h" // Definitions needed by the whole program
2020-08-16 02:03:43 +08:00
#include <Arduino.h> // Basic Arduino definitions
#include <SPI.h> // Serial Peripheral Interface library
#include <Wire.h> // 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
2020-08-16 02:03:43 +08:00
#if USE_WIFI // M0WID - We need the following if using WiFi
2020-08-16 02:50:26 +08:00
#include "simpleSA_wifi.h" // Our WiFI definitions
2020-08-16 02:03:43 +08:00
#include <WiFi.h> // ESP32 WiFi support library
#include <AsyncTCP.h> // Asynchronous TCP library for Espressif MCUs
#include "SPIFFS.h" // ESP32 built-in "file system"
#endif
#include <TFT_eSPI.h> // TFT_eSPI library
#include <Preferences.h> // Library to save and restore configuration information
#include "preferences.h" // Functions to write/read settings and config
#include "ui.h" // Touch screen interface definitions
/*
* Note the actual definitions for display and touch screen pins used are defined
* in the file "M0WID_Setup_ILI9341_TinySA.h" in the "User_Setups" directory of
2020-08-16 02:03:43 +08:00
* the "TFT_eSPI" library.
*
* These are based on using HSPI (spi2) for display and VSPI (spi3) for the SI4432s
* and the attenuator module.
*
* They are listed here just for reference; those not defined in the "TFT_eSPI"
* library are defined in the various other files comprising the program:
*
* GPIO-0 SD_CS Reserved for SD card chip select
* GPIO-1 RX0 Used by USB
* GPIO-2 TFT_RST TFT Reset; Could be set to -1 if using processor reset
* GPIO-2 tinySA_led The on-board LED flashes on data transfer
* GPIO-3 TX0 Used by USB
* GPIO-4 SI_RX_CS RX Si4432 Transceiver chip select
* GPIO-5 SI_TX_CS TX Si4432 Transceiver chip select
* GPIO-6 to 11 Reserved for the Flash memory
* GPIO-12/MISO2 TFT_MISO Display data input (to processor)
* GPIO-13/MOSI2 TFT_MOSI Display data output (from processor)
* GPIO-14/CLK2 TFT_SCLK Display SPI clock
* GPIO-15 TFT_CS Display chip select
* GPIO-16
* GPIO-17
* GPIO-18/CLK VSPI_SCLK Transceiver & Attenuator SPI clock
* GPIO-19/MISO VSPI_SDO Transceiver & Attenuator SPI data in (to processor)
* GPIO-21/SDA SDA PCF8575 Data line (VK3PE Implementation only)
* GPIO-21/PE4302_LE PE4302 attenuator Latch Enable (serial attenuator option)
* GPIO-22/SCL SCL PCF8575 Clock line (VK3PE Implementation only)
* GPIO-23/MOSI VSPI_SDI Transceiver & Attenuator SPI data out (from processor)
* GPIO-25 TFT_LED Display backlight intensity (PWM)
* GPIO-26 TOUCH_CS Touch screen chip select (in TFT_eSPI library setup)
* GPIO-27 TFT_DC Display data/command select line
* GPIO-32 ENC_PB Reserved for encoder pushbutton switch
* GPIO-33 TS_INT Reserved for touch screen interrupt request
* GPIO-33 ENC_BB or encoder backup button
* GPIO-34 ENC_B Reserved for encoder pin "B" (input only pin)
* GPIO-35 ENC_A Reserved for encoder pin "A" (input only pin)
* GPIO-36 Sensor_VP (input only pin)
* GPIO-39 Sensor_VN (input only pin)
* RX0 USB Receive
* TX0 USB Transmit
*/
/*
* The "sprites" are a method supported by the "TFT_eSPI" library that allow faster
* updates of the display and reduces flicker. We create three; one for the scan image
* proper, one for the data at the top of the screen and a separate one for the data
* at the top of the screen. We don't use one for the touch screen menus as there is
* no flicker problem there.
*/
TFT_eSPI tft = TFT_eSPI(); // The TFT display object proper
TFT_eSprite img = TFT_eSprite ( &tft ); // Sprite for the chart drawing
TFT_eSprite tSprite = TFT_eSprite ( &tft ); // Sprite for the top of chart display
TFT_eSprite sSprite = TFT_eSprite ( &tft ); // Sprite for the side of chart display
TFT_eSprite gainScaleSprite = TFT_eSprite ( &tft ); // Sprite for the gain scale
TFT_eSPI_Button key[SIG_KEY_COUNT]; // Used for signal generator modes for simplicity
/*
* Definition for signal generator keys
*/
sig_key_t sig_keys[SIG_KEY_COUNT] = {
// x, y, width, height, normal colour, active colour, text
// x and y are mid points
{ 18, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 0 100MHz+
{ 18+ NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 1 10MHz+
{ 18+ 2*NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 2 1MHz+
{130, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 3 100kHz+
{130+ NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 4 10kHz+
{130+ 2*NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 5 1kHz+
{237, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 6 100Hz+
// {237+ NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 10Hz+ Pointless - SI4432 has insufficient resolution and huge drift!
// {237+ 2*NUM_W, 88, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 1Hz+
{ 18, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 7 100MHz+
{ 18+ NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 8 10MHz+
{ 18+ 2*NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 9 1MHz+
{130, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 10 100kHz+
{130+ NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 11 10kHz+
{130+ 2*NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 12 1kHz+
{237 , 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 13 100Hz+
// {237+ NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 10Hz+ Pointless - SI4432 has insufficient resolution and huge drift!
// {237+ 2*NUM_W, 126, NUM_W, NUM_H, TFT_WHITE, TFT_CYAN, "", ""}, // 1Hz+
{ 45, 25, KEY_W, KEY_H, TFT_WHITE, TFT_CYAN, "SALo", "SALo"}, // 14 SALo
// { 45, 165, KEY_W, KEY_H, TFT_WHITE, TFT_GREEN, "FM", "FM on"}, // xx FM (frequency Modulation) on/off
// {105, 165, KEY_W, KEY_H, TFT_WHITE, TFT_GREEN, "AM", "AM on"}, // xx AM (amplitude modulation) on/off
{230, 175, KEY_W, KEY_H, TFT_WHITE, TFT_GREEN, "ON", "OFF"}, // 15 ON/OFF (Sig gen output on/off)
{285, 25, KEY_W, KEY_H, TFT_WHITE, TFT_CYAN, "Menu", "Menu"}, // 16 When pressed launch menu
{230, 25, KEY_W, KEY_H, TFT_WHITE, TFT_CYAN, "123", "123"} // 17 When pressed keypad to enter frequency
};
/*
* We create four sprites for the markers and an array of their addresses. The addresses
* will be passed to the marker objects when we initialize them in the "setup" function.
*
* It would make more sense to actually create these in the marker itself, but any attempts
* to do that caused newer versions of the "TFT_eSPI" library to crash or do goofy things.
*/
TFT_eSprite mkr1 = TFT_eSprite ( &tft ); // Sprites for the markers
TFT_eSprite mkr2 = TFT_eSprite ( &tft );
TFT_eSprite mkr3 = TFT_eSprite ( &tft );
TFT_eSprite mkr4 = TFT_eSprite ( &tft );
TFT_eSprite* mSprites[MARKER_COUNT] = { &mkr1, &mkr2, &mkr3, &mkr4 };
/*
* Create an array of un-initialized markers. We'll initialize them in the "setup"
* function.
*/
Marker marker[MARKER_COUNT]; // Array of marker objects
/*
* These are used for positioning the markers:
*/
peak_t peaks[MARKER_COUNT]; // Peaks in the current scan
peak_t oldPeaks[MARKER_COUNT]; // Peaks in the previous scan
/*
* The "vspi" object handles communications between the processor and the serial
* version of the PE4302 attenuator and the Si4432 modules. If we ever implement
* use of the SD card memory on the display, it will also use the VSPI bus.
*
* It's created as a pointer so it can be passed to the objects that use it.
*/
SPIClass* vspi = new SPIClass ( VSPI ); // Create VSPI object and a pointer to it
/*
* Constructors for PE4302 object - which one is used depends on the type set in My_SA.h
*/
#if ( PE4302_TYPE == PE4302_PCF) // Create the PCF8574 attenuator object
PE4302 att ( PCF8574_ADDRESS );
#endif
#if ( PE4302_TYPE == PE4302_GPIO ) // The parallel mode one
PE4302 att ( DATA_16, DATA_8, DATA_4, DATA_2, DATA_1, DATA_0 );
#endif
#if ( PE4302_TYPE == PE4302_SERIAL ) // The serial mode one
PE4302 att (vspi, PE4302_LE );
#endif
/*
* Create the transceiver objects:
*/
Si4432 rcvr ( vspi, SI_RX_CS, RX_4432 ); // Create object for the receiver
Si4432 xmit ( vspi, SI_TX_CS, TX_4432 ); // And the transmitter (local oscillator)
#ifdef SI_TG_IF_CS
Si4432 tg_if ( vspi, SI_TG_IF_CS, TGIF_4432 ); // Create object for the tracking generator IF SI4432
#endif
#ifdef SI_TG_LO_CS
Si4432 tg_lo ( vspi, SI_TG_LO_CS, TGLO_4432 ); // Create object for the tracking generator LO SI4432
#endif
/*
* Global definitions:
*/
bool tgIF_OK; // true if the tracking generator IF SI4432 is pesent and initialised OK
bool tgLO_OK; // true if the tracking generator LO SI4432 is pesent and initialised OK
uint8_t numberOfWebsocketClients; // How many connections
uint16_t wiFiPoints; // Push data to the wifi clinets when this many points collected
unsigned long wiFiTargetTime = WIFI_UPDATE_TARGET_TIME;
unsigned long websocketInterval = WEBSOCKET_INTERVAL;
uint16_t websocketFailCount;
2020-08-16 02:03:43 +08:00
#ifdef USE_WIFI
// Json document buffers
//size_t capacity = JSON_ARRAY_SIZE ( MAX_WIFI_POINTS + 1 )
// + ( MAX_WIFI_POINTS + 1 ) * JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE( 5 );
2020-08-16 02:03:43 +08:00
static DynamicJsonDocument jsonDocument ( 4000 ); // Buffer for json data to be pushed to the web clients
2020-08-16 02:03:43 +08:00
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;
2020-08-16 02:03:43 +08:00
/*
* Some varibales for the various operating modes
*/
2020-08-16 02:03:43 +08:00
uint16_t tinySA_mode = SA_LOW_RANGE; // Low frequency range
const char *modeText[] = { "SALo", "SAHi", "SGLo", "SGHi", "IFSw", "0SpL", "0SpH", "BScp", "RXSw" }; // For mode display
2020-08-16 02:03:43 +08:00
float bandwidth; // The current bandwidth (not * 10)
unsigned long delaytime = 2000; // delay time to allow SI4432 filters to settle
2020-08-16 02:03:43 +08:00
uint32_t steps = displayPoints; // Number of frequency steps in the sweep
2020-08-16 02:03:43 +08:00
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
double dBadjust; // Sum of attenuation, external gain, calibration offset and RBW correction
2020-08-16 02:03:43 +08:00
/*
* 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
2020-08-16 02:03:43 +08:00
int VFO = RX_4432; // Set current VFO for command parser to the receiver Si4432
int gainReading; // Current preamp gain (will vary during a scan if AGC enabled)
bool AGC_On; // Flag indicates if Preamp AGC is enabled
uint8_t AGC_Reg; // Fixed value for preampGain if not auto
uint8_t showRSSI = 0; // When zero don't show it (serial output only)
int changedSetting = true; // Something in the "setting" structure changed
int updateSidebar = false; // Flag to indicate update of sidebar is needed
// UI functions
extern uint8_t ui_mode; // What mode we are in (in "ui.cpp")
extern void StartSigGenMenu ( void ); // Function to launch sig gen menu
extern void StartSigGenFreq ( void ); // Function to set frequency
extern void ResetSAMenuStack ( void ); // Reinitialise stack for SA mode
extern void ResetIFsweepMenuStack ( void ); // Reinitialise stack for IF Sweep mode
extern void ResetRXsweepMenuStack ( void ); // Reinitialise stack for IF Sweep mode
2020-08-16 02:03:43 +08:00
extern void ResetBandscopeMenuStack ( void ); // Reinitialise stack for Bandscope mode
extern void ShowSplash ( void ); // Displays the splash screen
extern void pushSettings ();
extern void pushIFSweepSettings ();
extern void pushRXSweepSettings ();
2020-08-16 02:03:43 +08:00
extern void pushBandscopeSettings ();
/*
* Variables for IFSweep Mode
*/
uint32_t startFreq_IF = IF_SWEEP_START;
uint32_t stopFreq_IF = IF_SWEEP_STOP;
uint32_t sigFreq_IF = 15000000; // 15 Mhz reference
2020-08-16 02:03:43 +08:00
/*
* 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
2020-08-16 02:03:43 +08:00
/*
* Variables for timing websocket event checks:
*/
unsigned long lastWebsocketMicros; // For timing between websocket events
unsigned long loopStartMicros; // For timing the scan
//unsigned long loopMicros; // To report the scan time
/*
* Variables for measuring sweep time:
*/
unsigned long sweepStartMicros; // For timing the scan
unsigned long lastSweepStartMicros; // For timing the scan
unsigned long sweepMicros; // To report the scan time
uint32_t sweepCount; // Used to inhibit handling Wifi until
// two sweeps have been done and WiFi
// has stabilized
/*
* These arrays contain the data for the various scan points; they are used in
* here and in the "TinySA_wifi" modules:
2020-08-16 02:03:43 +08:00
*/
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
2020-08-16 02:03:43 +08:00
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 double old_startFreq = -1;
static double old_stopFreq = -1;
static int requiredRBW10 = 0;
static int old_requiredRBW10 = -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;
2020-08-16 02:03:43 +08:00
int16_t standalone = true;
uint16_t spacing = 10000;
bool paused = false;
uint16_t sigGenOutputOn = false;
2020-08-16 02:03:43 +08:00
uint32_t oldFreq; // to store the current Signal Generator frequency
/*
* "setActualPowerRequested" is set by menu or WiFi.
* When request is set the peak value during the sweep is used to calculate
* the offset needed to make the power reading match a user input level
* A calibration tool
*/
bool setActualPowerRequested = false;
float actualPower = CAL_POWER; // Defined in "My_SA.h"
int initSweep = true; // Flag to indicate sweep needs to restart from the beginning
// Set when sweep settings change
int16_t sweepStartDone = false; // Ensure initialize of sweep is only done once
/*
* All the following deal with the optional rotary encoder which could be used
* to handle the menu options.
*
* Note - None of the encoder stuff has been implemented yet, but we'll leave the
* definitions here for the time being:
*
* Symbols for button events and states (change to UC if we ever use them):
*/
#ifdef USE_ROTARY // Not defined anywhere!
enum buttont_event { shortClickRelease=1, longClick=2,
longClickRelease=3, shortBackClickRelease=4,
longBackClick=5, longBackClickRelease=6,
buttonRotateUp=7, buttonRotateDown=8 };
enum button_state { buttonUp, buttonDown, buttonLongDown };
int buttonState = buttonUp; // The current reading from the encoder switch
int backButtonState = buttonUp; // The current reading from the backup button
int buttonEvent = 0; // Short click release?
int lastButtonRead = HIGH; // The previous reading from the encoder switch
int lastBackButtonRead = HIGH; // The previous reading from the backup button
uint32_t lastDebounceTime = 0; // The last time the encoder switch was toggled
uint32_t debounceDelay = 50; // The debounce time; increase if the output flickers
uint32_t longPressDelay = 350; // The long press time; increase if the output flickers
int32_t incr;
int32_t incrBase = 1000000;
int incrBaseDigit = 7;
#endif // "#ifdef USE_ROTARY"
settings_t setting; // Structure to track & save settings
sigGenSettings_t sigGenSetting; // settings for signal generator mode
trackGenSettings_t trackGenSetting; // parameters for tracking gen mode
Preferences preferences; // "preferences" is an object to enable
// saving data to Flash
/*
* "setup" initializes everything:
*/
void setup ()
{
bool fsStatus = false; // True if SPIFFS loads ok
Serial.begin ( 115200 ); // Start up the USB connection
tft.begin (); // Start the display object
tft.setRotation ( 3 ); // If upside down, change to '1'
/*
* Disabling the use of the PSRAM on the WROVER boards for the TFT_eSPI library
* should speed things up slightly. We do the same thing for the sprites.
*/
tft.setAttribute ( PSRAM_ENABLE, false ); // Forces all future Sprites to be created in the on-board RAM area
preferences.begin ( "tinySA", false ); // We retreive stuff from flash memory
ReadConfig (); // Read menu and touch settings
ReadSettings(); // Read attenuation, level adjustment etc
ReadSigGenSettings(); // Values for signal generator mode
ReadTrackGenSettings(); // Values for tracking generator mode
setting.ShowStorage = false; // Display stored scan (on or off)
setting.SubtractStorage = false; // Subtract stored scan (on or off)
/*
* The touch screen needs to be calibrated. In previous versions, the instructions
* were to un-comment the call to the "TouchCalibrate" here the first time you
* ran the software and to insert the numbers it gave you into the code.
*
* This is no longer necessary as the configuration can now be done from the touch
* acreen menu system, but we leave it here as some displays are nowhere near correct
* and if they are way off, you won't be able to access the touch menus at all.
*
* The "config.touch_cal" fed into the "tft.setTouch" function is also reacalled
* from the flash memory upon startup, so one should only need to calibrate the
* touch screen one time.
*/
// TouchCalibrate (); // Comment out after first run
tft.setTouch ( config.touch_cal ); // Send calibration data to the display
#ifdef BACKLIGHT_LEVEL
setUpLEDC(); // Set up the backlight control
#endif
/*
* Display the splash screen:
*/
ShowSplash (); // Display the splash screen
/*
* Set up the pins used for the VSPI bus used by both SI4432 modules and the serial
* mode PE4302 attenuator module and initialize the bus object:
*/
pinMode ( V_SCLK, OUTPUT ); // SPI Clock is an output
pinMode ( V_SDO, INPUT ); // SDO (MISO) is an input
pinMode ( V_SDI, OUTPUT ); // SDI (MOSI) is an output
digitalWrite ( V_SCLK, LOW ); // Make SPI clock LOW
digitalWrite ( V_SDI, LOW ); // Along with MOSI
vspi->begin ( V_SCLK, V_SDO, V_SDI ); // Start the VSPI: SCLK, MISO, MOSI
// SI4432 SPI rated for 10MHz according to data sheet. Seems OK at 16MHz
vspi->setFrequency(BUS_SPEED); // run at 10MHz
// tft.println("VSPI started");
/*
* Initialize the SI4432 for RX and LO(TX)
*/
tft.setCursor(0,160);
// tft.println ( "Initializing TX SI4432" );
if ( ! xmit.Init ( config.TX_capacitance ) ) // Initialize the transmitter module
DisplayError ( ERR_FATAL,
"LO SI4432 failed",
"to initialise", NULL, NULL );
// tft.println( "Initializing RX SI4432" );
if ( ! rcvr.Init ( config.RX_capacitance ) ) // Initialize the receiver module
DisplayError ( ERR_FATAL,
"RX SI4432 failed",
"to initialise", NULL, NULL );
bandwidth = rcvr.SetRBW ( setting.Bandwidth10, &delaytime ); // Set initial bandwidth and delaytime
SetRX ( 0 ); // RX in receive, LO in transmit
rcvr.SetPreampGain ( setting.PreampGain );
// If the tracking generator IF SI4432 is defined, then check to see if it is connected
tgIF_OK = false;
2020-08-16 02:03:43 +08:00
#ifdef SI_TG_IF_CS
// pinMode (SI_TG_IF_CS, INPUT); //Start off in input mode, no pullup
// delay(2);
// if (digitalRead (SI_TG_IF_CS) ) {
// // Tracking gen IF present - initialise it
// pinMode (SI_TG_IF_CS, OUTPUT);
// delay(2);
// tgIF_OK = tg_if.Init ( config.tgIF_capacitance );
tft.setCursor(0,180);
tft.println ( "Initializing track gen IF SI4432" );
2020-08-16 02:03:43 +08:00
if ( tg_if.Init ( config.tgIF_capacitance ) )
{
tft.println ("Tracking IF initialised");
tgIF_OK = true;
}
else
{
DisplayError ( ERR_WARN,
"Track gen IF SI4432 failed",
"to initialise", NULL, NULL );
tgIF_OK = false;
}
// }
// else
// {
// Serial.println("Track gen IF SI4432 not detected");
// pinMode (SI_TG_IF_CS, OUTPUT);
// digitalWrite(SI_TG_IF_CS, HIGH); // make sure it doesn't get selected
// }
#else
pinMode (2, OUTPUT);
digitalWrite(2, HIGH); // make sure it doesn't get selected
#endif
// If the tracking generator LO SI4432 is defined, then check to see if it is connected
tgLO_OK = false;
2020-08-16 02:03:43 +08:00
#ifdef SI_TG_LO_CS
// pinMode (SI_TG_LO_CS, INPUT); //Start off in input mode, no pullup
// delay(2);
// if (digitalRead (SI_TG_LO_CS) ) {
// // Tracking gen LO present - initialise it, but not in TX mode yet
// pinMode (SI_TG_LO_CS, OUTPUT);
// delay(2);
tft.println ( "Initializing track gen LO SI4432" );
2020-08-16 02:03:43 +08:00
tgLO_OK = tg_lo.Init ( config.tgLO_capacitance );
if ( tgLO_OK )
tft.println ("Tracking LO initialised");
else
DisplayError ( ERR_WARN,
"Track gen LO SI4432 failed",
"to initialise", NULL, NULL );
// }
// else
// {
// Serial.println("Track gen LO SI4432 not detected");
// pinMode (SI_TG_LO_CS, OUTPUT);
// digitalWrite(SI_TG_LO_CS, HIGH); // make sure it doesn't get selected
// }
#else
pinMode (25, OUTPUT);
digitalWrite(25, HIGH); // make sure it doesn't get selected
2020-08-16 02:03:43 +08:00
#endif
/*
* Initialize the SPIFFS system
*/
fsStatus = SPIFFS.begin (); // Mount the file system
if ( !fsStatus ) // If we can't access the file system
{
Serial.println ( "SPIFFS Mount Failed" ); // Error message to serial output
DisplayError ( ERR_WARN,
"Failed to mount SPIFFS",
"Use ESP sketch Data Upload",
"In the Arduino IDE",
"WiFi Disabled" );
}
// printSPIFFS (); // Print out the contents of SPIFFS to the serial port
#if ( USE_WIFI ) // see My_SA.h
if ( fsStatus ) // If we can access the file system
{
#ifdef USE_ACCESS_POINT // If using a WiFi access point
startAP (); // Start it up
#else // Connect to the router using SSID
// and password defined in TinySA.h
2020-08-16 02:03:43 +08:00
Serial.println ( "Connecting..." ); // Indicate trying to connect
if ( connectWiFi () ) // Connection established?
Serial.println( "Connected" ); // We are connected!
else
Serial.println( "Connection Failed!" );
#endif // "#ifdef USE_ACCESS_POINT"
/*
* Start the websockets server and event handler
*/
// tft.println("Starting Websockets");
webSocket.begin ();
webSocket.onEvent ( webSocketEvent );
Serial.println ( "WebSockets started" );
websocketInterval = WEBSOCKET_INTERVAL;
// tft.println("Building Server");
buildServer ();
IPAddress ipAddress = WiFi.localIP (); // Get our IP address
Serial.print ( "Setup - WiFi access point started - browse to http://" );
Serial.println ( ipAddress.toString().c_str() );
}
#endif // "#if ( USE_WIFI )"
if ( !USE_WIFI || !fsStatus ) // If the WiFI option is not enabled
Serial.println ( "\nWiFi not enabled!\n" );
// Serial.println ( "Initializing PE4302" );
// tft.println("Initialising PE4302");
att.Init (); // Initialize the PE4302 attenuator
att.SetAtten ( 0 ); // Set the attenuation to zero
// tft.println("Setup Complete");
Serial.printf ( "\nsimpleSA %s Initialization Complete\n", PROGRAM_VERSION );
2020-08-16 02:03:43 +08:00
ShowMenu (); // Display the menu of commands on serial monitor
/*
* Initialize the markers. The objects need the address of the display object and
* the individual marker objects previously created.
*/
for ( int i = 0; i < MARKER_COUNT; i++ )
{
marker[i].Init ( mSprites[i], i + 1 ); // Pass in the sprite object
marker[i].Status ( setting.MkrStatus[i] ); // Set color and enabled status
}
delay ( 3000 ); // Time to read splash and initialize messages - Read fast!
ClearDisplay ();
setMode ( setting.Mode ); // setMode initializes stuff for the selected mode
lastWebsocketMicros = micros();
} // End of "setup"
/*
* ########################################################################################
*
* "loop" runs forever and handles all the things needed to make it work.
*
* #######################################################################################
*/
void loop ()
{
loopStartMicros = micros();
#if USE_WIFI
//Serial.println("l");
if ( ( numberOfWebsocketClients > 0) || (loopStartMicros - lastWebsocketMicros > websocketInterval)
|| (loopStartMicros < lastWebsocketMicros) ) // handles rollover of micros()
{
// Serial.print("L");
webSocket.loop (); // Check websockets for events, but not at first
// Serial.println("l");
lastWebsocketMicros = loopStartMicros;
}
#endif
CheckCommand (); // Anything from the serial input?
if ( ( setting.Mode != SIG_GEN_LOW_RANGE ) || (ui_mode != UI_NORMAL ) )
{
UiProcessTouch (); // Handle the touch screen
if ( ui_mode != UI_NORMAL )
{
initSweep = true;
return; // If in menu don't do anything else
}
}
/*
* Not fully implemented yet.
*/
switch ( setting.Mode )
{
case SA_LOW_RANGE:
doSweepLow(); // Spectrum Analyser Low Frequency range
break;
case SA_HIGH_RANGE:
doSweepHigh(); // Spectrum Analyser High Frequency range
break;
case SIG_GEN_LOW_RANGE:
doSigGenLow(); // Signal Generator Low Frequency range
break;
case SIG_GEN_HIGH_RANGE:
doSigGenHigh(); // Signal Generator High Frequency range
break;
case IF_SWEEP:
doIF_Sweep(); // IF Sweep
break;
case RX_SWEEP:
doRX_Sweep(); // RX Sweep
break;
2020-08-16 02:03:43 +08:00
case BANDSCOPE:
doBandscope(); // Bandscope Sweep
break;
default:
DisplayError ( ERR_WARN,
"Invalid Mode!",
"Mode changed to",
"Analayze Low range", NULL );
initSweepLow ();
} // end of switch
} // end of loop
/*
* SetMode initializes various things for the selected mode then changes mode. It is called
* by the web interface, command line or menu.
*/
void setMode ( uint16_t newMode )
{
switch ( newMode )
{
case SA_LOW_RANGE:
initSweepLow();
break;
// case SA_HIGH_RANGE:
// initSweepHigh();
// break;
case SIG_GEN_LOW_RANGE:
initSigLow();
break;
// case SIG_GEN_HIGH_RANGE:
// initSigHigh();
// break;
case BANDSCOPE:
initBandscope();
break;
2020-08-16 02:03:43 +08:00
case IF_SWEEP:
initIF_Sweep();
break;
case RX_SWEEP:
initRX_Sweep();
2020-08-16 02:03:43 +08:00
break;
2020-08-16 02:03:43 +08:00
default:
DisplayError ( ERR_WARN,
"Invalid Mode!",
"Mode not changed",
NULL, NULL );
}
}
/*
* "menuExit" is called when the user leaves the menu
* The required action depends on the selected mode, and whether or not the
* device is already in the selected mode
*/
void menuExit()
{
switch ( tinySA_mode )
{
case SA_LOW_RANGE:
if ( setting.Mode == SA_LOW_RANGE )
RedrawHisto();
else
initSweepLow();
break;
case SA_HIGH_RANGE:
initSweepHigh();
break;
case SIG_GEN_LOW_RANGE:
initSigLow();
break;
case SIG_GEN_HIGH_RANGE:
initSigHigh();
break;
case IF_SWEEP:
if ( setting.Mode == IF_SWEEP )
RedrawHisto();
else
initIF_Sweep();
break;
case RX_SWEEP:
if ( setting.Mode == RX_SWEEP )
RedrawHisto();
else
initRX_Sweep();
break;
2020-08-16 02:03:43 +08:00
case BANDSCOPE:
if ( setting.Mode == BANDSCOPE )
RedrawHisto();
else
initBandscope();
break;
default:
// add handler here
break;
} // end of switch
}
/*
* Initialise common to low, high, RX and IF_Sweeps
2020-08-16 02:03:43 +08:00
*/
void init_sweep()
{
ClearDisplay ();
/*
* Set up the "img" Sprite. This is the image for the graph. It makes for faster display
* updates and less flicker.
*
* 16 bit colour depth is faster than 8 and much faster than 4 bit! BUT - sprites
* pushed to it do not have correct colour - 8 bit and it is fine.
*
* All marker sprites are WHITE for now.
*/
tft.unloadFont();
img.unloadFont();
img.deleteSprite();
img.setTextSize ( 1 );
img.setColorDepth ( 16 );
img.setAttribute ( PSRAM_ENABLE, false ); // Don't use the PSRAM on the WROVERs
img.createSprite ( 2, gridHeight + 1 ); // Only 2 columns wide
2020-08-16 02:03:43 +08:00
/*
* The "tSprite" is used for displaying the data above the scan grid.
*/
tSprite.deleteSprite();
tSprite.setRotation ( 0 );
tSprite.setTextSize ( 1 );
tSprite.setColorDepth ( 16 );
tSprite.setAttribute ( PSRAM_ENABLE, false ); // Don't use the PSRAM on the WROVERs
tSprite.createSprite ( tft.width() - X_ORIGIN, Y_ORIGIN );
/*
* The "sSprite" is used for displaying the data to the side of the scan grid.
*/
sSprite.unloadFont();
sSprite.deleteSprite();
sSprite.setTextSize ( 1 );
sSprite.setColorDepth ( 16 );
sSprite.setAttribute ( PSRAM_ENABLE, false ); // Don't use the PSRAM on the WROVERs
/*
* Create and draw the sprite for the gain scale
*/
CreateGainScale ();
// Make sure everything will be reset
old_settingAttenuate = -1000;
old_settingPowerGrid = -1000;
old_settingMax = -1;
old_settingMin = -1;
old_startFreq = -1;
old_stopFreq = -1;
old_requiredRBW10 = -1;
2020-08-16 02:03:43 +08:00
old_vbw = -1;
old_settingAverage = -1;
old_settingSpur = -100;
old_bandwidth = 0;
2020-08-21 19:33:06 +08:00
SetRX ( 0 ); // LO to transmit, RX to receive
xmit.SetOffset ( 0 ); // make sure frequency offset registers are zero
2020-08-16 02:03:43 +08:00
xmit.SetDrive ( setting.Drive ); // Set transmitter power level
rcvr.SetPreampGain ( setting.PreampGain );
#ifdef SI_TG_IF_CS
if (tgIF_OK) {
2020-08-21 19:33:06 +08:00
tg_if.TxMode ( trackGenSetting.IF_Drive ); // turn on the IF oscillator in tracking generator
2020-08-16 02:03:43 +08:00
}
#endif
#ifdef SI_TG_LO_CS
if (tgLO_OK) {
2020-08-21 19:33:06 +08:00
tg_lo.TxMode ( trackGenSetting.LO_Drive ); // turn on the Local Oscillator in tracking generator
2020-08-16 02:03:43 +08:00
}
#endif
sweepStartDone = false; // Make sure this initialize is only done once per sweep
initSweep = true;
}
/*
* Initialise the JSON document that is used to push the sweep data to the web clients
* Used in all the sweep modes
*/
void initChunkSweepDoc (uint32_t startIndex)
{
jsonDocument.clear ();
jsonDocument["PreAmp"] = setting.PreampGain; // Fixed gain
jsonDocument["mType"] = "chunkSweep";
jsonDocument["StartIndex"] = startIndex;
jsonDocument["sweepPoints"] = sweepPoints;
jsonDocument["sweepTime"] = (uint32_t)(sweepMicros/1000);
}
2020-08-16 02:03:43 +08:00
/*
* Initialise high frequency mode sweep.
*/
void initSweepHigh ()
{
// To be done!
DisplayError ( ERR_WARN,
"Sweep not done!",
"Mode changed to",
"Analayze Low range", NULL );
tinySA_mode = SA_LOW_RANGE;
setting.Mode = tinySA_mode;
}
/*
* High freq range sweep - not yet implemented
*
* Will use signals direct into LO SI4432, which be in receive
* no mixer, attenuator or low pass filter
*/
void doSweepHigh ()
{
DisplayError ( ERR_WARN,
"Sweep High Range",
"not implemented.",
"Low range mode",
"selected for you!" );
initSweepLow();
}
/*
* Initialise sig gen high frequency mode.
* May turn out to not be needed!
*/
void initSigHigh ()
{
// To be done!
DisplayError ( ERR_WARN,
"IF Sweep not done!",
"Mode changed to",
"Analayze Low range", NULL );
tinySA_mode = SA_LOW_RANGE;
setting.Mode = tinySA_mode;
}
/*
* High freq range signal generator - not yet implemented
* May turn out to be included in sig gen mode and auto
* transition depending on set frequency
*/
void doSigGenHigh ()
{
DisplayError ( ERR_WARN,
"Signal Generator High Range",
"not implemented.",
"Low range SA mode",
"selected for you!" );
initSweepLow();
}
/*
* "SetRX" - Mode 3 is sig gen, 0 is normal (RX as receive, LO on), 1 is both
* receiving 2 not used
*/
void SetRX ( int p )
{
int saRX = p;
if ( saRX == ( 3 )) // Both on TX - sig gen mode
{
rcvr.TxMode ( sigGenSetting.RX_Drive ); // Put receive module in TX mode
xmit.TxMode ( sigGenSetting.LO_Drive ); // Put transmit module in TX mode
}
else
{
if ( saRX == 0 ) // Normal configuration
{
rcvr.RxMode (); // Put receive module in RX mode
xmit.TxMode ( setting.Drive ); // Put transmit (LO) module in TX mode
}
else if ( saRX == 1 ) // Both in receive mode
{
rcvr.RxMode (); // Put receive module in RX mode
xmit.RxMode (); // Put transmit module in RX mode
}
else if ( saRX == 2 ) // Normal configuration
{
rcvr.RxMode (); // Put receive module in RX mode
xmit.TxMode ( setting.Drive ); // Put transmit module in TX mode
}
}
}
/*
* "ClearDisplay" - Function to clear the display.
*/
void ClearDisplay ()
{
tft.fillScreen ( BLACK ); // Fade to black
}
/*
* "textWhite" sets the text size to '1' and the text color to "WHITE".
*/
void textWhite()
{
tft.setTextSize ( 1 );
tft.setTextColor ( WHITE ); // Draw white text (transparent background)
}
/*
* Find out if the touch was in the bounds of the slider
*/
bool sliderPressed( bool pressed, uint16_t t_x, uint16_t t_y )
{
uint16_t minX = SLIDER_X - 5;
uint16_t maxX = SLIDER_X + SLIDER_WIDTH + 2 * SLIDER_KNOB_RADIUS + 5;
uint16_t minY = SLIDER_Y;
uint16_t maxY = SLIDER_Y + 2 * SLIDER_KNOB_RADIUS;
return( ( (t_x >= minX) && (t_x <= maxX) && (t_y >= minY) && (t_y <= maxY) ) && pressed );
}
float sliderPercent( uint16_t t_x )
{
float p = (float)(t_x - SLIDER_X - SLIDER_KNOB_RADIUS) * 100.0 / (float)SLIDER_WIDTH;
//Serial.printf("t_x: %i p: %f SLIDER_X: %i SLIDER_KNOB_RADIUS: %i \n", t_x, p, SLIDER_X, SLIDER_KNOB_RADIUS);
if ( p > 100.0 )
p = 100.0;
if ( p < 0.0 )
p = 0.0;
return( p );
}
/*
* Draw a slider control. Uses the sSprite.
*/
void drawSlider (uint16_t x, uint16_t y, float pos, float value, const char *unit) {
// range check
float p = pos;
if (p < 0) p=0.0;
if (p > 100.0) p=100.0;
// draw a rectangle first, then bar, then position the "knob" on top
sSprite.fillSprite(SIG_BACKGROUND_COLOR);
sSprite.fillRoundRect( SLIDER_KNOB_RADIUS, ( sSprite.height() - SLIDER_BOX_HEIGHT ) / 2,
SLIDER_WIDTH, SLIDER_BOX_HEIGHT, SLIDER_BOX_HEIGHT/4, SLIDER_BOX_COLOR);
uint16_t knob_x = pos * SLIDER_WIDTH / 100 + SLIDER_KNOB_RADIUS;
sSprite.fillRoundRect( SLIDER_KNOB_RADIUS, ( sSprite.height() - SLIDER_BOX_HEIGHT ) / 2,
knob_x - SLIDER_KNOB_RADIUS, SLIDER_BOX_HEIGHT, SLIDER_BOX_HEIGHT/4, SLIDER_FILL_COLOR);
sSprite.fillCircle(knob_x, sSprite.height()/2, SLIDER_KNOB_RADIUS, SLIDER_KNOB_COLOR);
// Draw the value
sSprite.setCursor(SLIDER_WIDTH + 2 * SLIDER_KNOB_RADIUS + 1, SLIDER_KNOB_RADIUS - 3);
sSprite.printf("%3.0f %s", value, unit);
// Serial.println("drawSlider");
sSprite.pushSprite(SLIDER_X, SLIDER_Y);
}
/*
* "DisplayError" - Added by M0WID to display an error message of up to three lines
* on the display.
*
* Modified in Version 2.7 by WA2FZW:
*
* Added another line and error levels. Also use a much better looking font
* to display the error message! Instead of painting the entire screen in
* the appropriate color for the error level, we use a rounded filled rectangle
* around the message; looks really nice!
*/
void DisplayError ( uint8_t severity, const char *line1, const char *line2, const char *line3, const char *line4 )
{
char line0[20]; // Line 0 of the message
const char *lines[] = { line0, line1, line2, line3, line4 }; // Pointers to the lines
ClearDisplay (); // Fade to black
if ( severity == ERR_INFO ) // Informational message?
{
strcpy ( line0, "Information:" ); // Set line 0
tft.fillRoundRect ( 0, 40, 320, 150, 10, WHITE ); // White background
}
else if ( severity == ERR_WARN ) // Warning message?
{
strcpy ( line0, "Warning:" ); // Set line 0
tft.fillRoundRect ( 0, 40, 320, 150, 10, YELLOW ); // Yellow background
}
else if ( severity == ERR_FATAL ) // Fatal error?
{
strcpy ( line0, "Fatal Error:" ); // Set line 0
tft.fillRoundRect ( 0, 40, 320, 150, 10, RED ); // Red background
}
tft.setTextColor ( BLACK ); // Black text
tft.setFreeFont ( &FreeSansBold9pt7b ); // Select nice font
tft.setCursor ( 20, 70 ); // Cursor for the first line
for ( int i = 0; i < 5; i++ ) // Display the lines
{
if ( lines[i] != NULL ) // Ignore any NULL lines
{
tft.print ( lines[i] );
tft.setCursor ( 20, 100 + ( i * 20 )); // Cursor on the next line
}
}
delay ( DELAY_ERROR * 1000 ); // Allow time to read it
if ( severity == ERR_FATAL ) // If fatal error
while ( true ) {} // Halt execution
ClearDisplay ();
tft.setFreeFont ( NULL ); // Set everything back to the default font
tft.setTextColor ( WHITE ); // and default text color
tft.setTextDatum ( TL_DATUM ); // and default position datum
}
/*
* "RedrawHisto" - Reset all old values to force redraw of labels
* Called when exiting menu and should be called when changed from web
*/
void RedrawHisto ()
{
delayMicroseconds(200); // short delay to make sure any SI4432 frequency updates are completed
initSweep = true;
memset ( peaks, 0, sizeof ( peak_t )); // peaks are copied to oldPeaks at start of sweep
old_settingAttenuate = 1000;
old_settingPowerGrid = -1000;
old_settingMax = -1;
old_settingMin = -1;
old_startFreq = -1;
old_stopFreq = -1;
old_settingAverage = -1;
old_settingSpur = -100;
ClearDisplay();
switch (tinySA_mode)
{
case BANDSCOPE:
DisplayBandscopeInfo();
break;
default:
DisplayInfo();
break;
}
DrawFullCheckerBoard();
2020-08-16 02:03:43 +08:00
}
/*
* "DisplayInfo" - Draws the background text around the checkerboard. Called
* when a setting is changed to set axis labels and top info bar
*/
void DisplayInfo ()
{
const char *averageText[] = { " OFF", " MIN", " MAX", " 2", " 4", " 8" };
const char *referenceOutText[] = { " 30", " 15", " 10", " 4", " 3", " 2", " 1" };
double fStart;
double fCenter;
double fStop;
// enum { SA_LOW_RANGE, SA_HIGH_RANGE, SIG_GEN_LOW_RANGE, SIG_GEN_HIGH_RANGE, IF_SWEEP, ZERO_SPAN_LOW_RANGE, ZERO_SPAN_HIGH_RANGE, TRACKING_GENERATOR, RX_SWEEP };
2020-08-16 02:03:43 +08:00
tSprite.fillSprite ( BLACK );
tSprite.setTextColor ( WHITE );
/*
* Update side bar info
*/
if ( initSweep || changedSetting || updateSidebar )
{
// Serial.println ( "DisplayInfo - InitSweep True" );
sSprite.createSprite ( X_ORIGIN, SCREEN_HEIGHT );
sSprite.fillSprite( BLACK );
sSprite.setCursor ( 0, 0 );
sSprite.setTextColor ( WHITE );
sSprite.print ( setting.PowerGrid );
sSprite.println ( "dB" );
sSprite.setCursor ( 0, CHAR_HEIGHT * 2 );
sSprite.setTextColor ( DB_COLOR );
sSprite.printf ( "%4i", maxGrid );
2020-08-16 02:03:43 +08:00
sSprite.setCursor ( 0, gridHeight + yOrigin );
sSprite.printf ( "%4i", minGrid );
2020-08-16 02:03:43 +08:00
sSprite.setTextColor ( WHITE );
sSprite.setCursor ( 0, HALF_CHAR_H * 8 );
sSprite.setTextColor ( GREEN );
if ( setting.Bandwidth10 !=0 ) // 0 = auto
sSprite.setTextColor ( ORANGE );
sSprite.println ( "RBW" );
if ( bandwidth >= 100 )
sSprite.printf ( "%4.0f\n", bandwidth );
else
sSprite.printf ( "%4.1f\n", bandwidth );
sSprite.setTextColor ( WHITE );
sSprite.setCursor ( 0, HALF_CHAR_H * 13 );
sSprite.printf ( "VRe\n%4i", vbw ); // Video resolution (not VBW)
sSprite.setCursor ( 0, HALF_CHAR_H * 18 );
if ( setting.PreampGain == 0x60 ) // AGC on
{
sSprite.setTextColor ( GREEN );
sSprite.println ( "Gain" );
sSprite.print ( "auto" );
}
else
{
sSprite.setTextColor ( ORANGE ); // Show value in red if not auto
sSprite.println ( "Gain" );
int lnaGain = 5; // Assume low gain
if ( setting.PreampGain & LNAGAIN ) // If LNA is turned on
lnaGain = 25; // High range
int pgaGain = ( setting.PreampGain & PGAGAIN ) * 3; // 3dB per bit
sSprite.printf ( "%4i", lnaGain + pgaGain );
}
sSprite.setTextColor ( WHITE );
sSprite.setCursor ( 0, HALF_CHAR_H * 23 );
sSprite.printf ( "Attn\n%4i", setting.Attenuate );
sSprite.setCursor ( 0, HALF_CHAR_H * 28 ); // Start at top-left corner
sSprite.printf ( "Ext\n%4.1f", setting.ExternalGain ); // Place holder for external gain/attenuation value
2020-08-16 02:03:43 +08:00
sSprite.setTextColor ( WHITE );
if ( setting.Average )
sSprite.setTextColor ( AVG_COLOR );
sSprite.setCursor ( 0, HALF_CHAR_H * 33 );
sSprite.println ( "Avg" );
sSprite.print ( averageText[setting.Average] );
sSprite.setTextColor ( WHITE );
sSprite.setCursor ( 0, HALF_CHAR_H * 38 );
sSprite.println ( "Spur" );
if ( setting.Spur )
sSprite.print ( " ON" );
else
sSprite.print ( " OFF" );
sSprite.setCursor ( 0, HALF_CHAR_H * 43 );
sSprite.println ( "Ref" );
if ( setting.ReferenceOut >= 0 && setting.ReferenceOut < 7 )
sSprite.print ( referenceOutText[setting.ReferenceOut] );
else
sSprite.print ( " OFF" );
sSprite.setCursor ( 0, HALF_CHAR_H * 48 );
sSprite.println ( "Trk" );
if ( trackGenSetting.Mode == 1 )
sSprite.print ( " ON" );
else if (trackGenSetting.Mode == 2 )
sSprite.print ( " SIG" );
else
sSprite.print ( " OFF" );
if ( USE_WIFI )
{
sSprite.setCursor ( 0, HALF_CHAR_H * 53 ); // Show number of connected clients
sSprite.printf ( "Web\n%4u", numberOfWebsocketClients );
}
sSprite.pushSprite ( 0, 0 );
sSprite.deleteSprite(); // Save memory
} // End of "if ( initSweep || changedSetting )"
/*
* Update frequency labels at bottom if changed
*/
tft.setTextColor ( WHITE,BLACK );
tft.setTextSize ( 1 );
if (tinySA_mode == IF_SWEEP) {
fStart = startFreq_IF/1000000.0;
fCenter = (double) ((( startFreq_IF + stopFreq_IF ) / 2.0 ) / 1000000.0 );
fStop = stopFreq_IF/1000000.0;
}
else 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;
}
2020-08-16 02:03:43 +08:00
else
{
fStart = (( (double)setting.ScanStart )/ 1000000.0); // Start freq
fCenter = (double) ((( setting.ScanStart + setting.ScanStop ) / 2.0 ) / 1000000.0 );
fStop = (( (double)setting.ScanStop )/ 1000000.0 ); // Stop freq
}
if ( old_startFreq != fStart || old_stopFreq != fStop )
{
tft.fillRect ( xOrigin, SCREEN_HEIGHT -
CHAR_HEIGHT, SCREEN_WIDTH - xOrigin - 1, SCREEN_HEIGHT - 1, BLACK );
2020-08-16 02:03:43 +08:00
// Show operating mode
tft.setCursor ( xOrigin + 50, SCREEN_HEIGHT - CHAR_HEIGHT );
2020-08-16 02:03:43 +08:00
tft.setTextColor ( DB_COLOR );
tft.printf ( "Mode:%s", modeText[setting.Mode] );
tft.setTextColor ( WHITE );
tft.setCursor ( xOrigin + 2, SCREEN_HEIGHT - CHAR_HEIGHT );
2020-08-16 02:03:43 +08:00
tft.print ( fStart );
// tft.print( "MHz" );
tft.setCursor ( SCREEN_WIDTH - 37, SCREEN_HEIGHT - CHAR_HEIGHT );
tft.print ( fStop );
// tft.print ( "MHz");
/*
* Show the center frequency:
*/
tft.setCursor ( SCREEN_WIDTH / 2 - 40 + xOrigin, SCREEN_HEIGHT - CHAR_HEIGHT );
2020-08-16 02:03:43 +08:00
tft.print ( fCenter );
tft.print ( "(MHz)" );
old_startFreq = fStart; // Save current frequency range
old_stopFreq = fStop; // For next time
}
tft.setCursor ( 220, SCREEN_HEIGHT - CHAR_HEIGHT ); // Show sweep time
tft.printf ( "%6ums", sweepMicros / 1000 );
// tft.setCursor ( 100, SCREEN_HEIGHT - CHAR_HEIGHT ); // Show number of connected clients
// tft.print( numberOfWebsocketClients );
/*
* We use the "tSprite" to paint the data at the top of the screen to avoid
* flicker.
*/
tSprite.setCursor ( 0, 0 );
tSprite.print ( "/" ); // Nasty!
/*
* Show marker values:
*
* The "xPos" and "yPos" arrays are the coordinates of where to place the marker data.
*
* The "posIndex" variable keeps track of the next available position for the marker
* data. If we want fixed positions for each marker, then change the "xPos" and "yPos"
* indicies to use "m".
*/
int xPos[MARKER_COUNT] = { 20, 20, 160, 160 };
int yPos[MARKER_COUNT] = { 0, CHAR_HEIGHT, 0, CHAR_HEIGHT };
int posIndex = 0;
for ( int m = 0; m < MARKER_COUNT; m++ )
{
tSprite.setCursor ( xPos[m], yPos[m] );
if (( marker[m].isEnabled()) && ( setting.ShowSweep || setting.Average != AV_OFF ))
{
tSprite.setTextColor ( WHITE );
tSprite.printf ( "%u:%5.1fdBm %8.4fMHz", marker[m].Index()+1,
rssiTodBm ( oldPeaks[m].Level ), oldPeaks[m].Freq / 1000000.0 );
}
else
{
tSprite.setTextColor ( DARKGREY );
tSprite.printf ( "%u:", marker[m].Index()+1 );
}
posIndex++;
}
int x = tSprite.width () - 45;
tSprite.setTextColor ( WHITE );
tSprite.pushSprite ( xOrigin, 0 ); // Write sprite to the display
2020-08-16 02:03:43 +08:00
updateSidebar = false;
} // End of "DisplayInfo"
/*
* Draw the complete checkerboard
* This can be optimized if needed
*/
void DrawFullCheckerBoard()
{
for ( int p=0; p<displayPoints; p++ )
2020-08-16 02:03:43 +08:00
{
DrawCheckerBoard ( p );
if ( p > 0 )
img.pushSprite( xOrigin+p-1, yOrigin );
2020-08-16 02:03:43 +08:00
}
// Serial.println ( "DrawFullCheckerBoard" );
}
/*
* "DrawCheckerBoard" - Paints the grid. It now uses a sprite so no need to
* erase the old grid, just draw a new one.
*
* The img sprite is two pixels wide, and the image from the previous data point
2020-08-16 02:03:43 +08:00
* is scrolled and then new data point drawn. The data from last is scrolled
* so any line from before is retained.
*/
void DrawCheckerBoard ( int x )
{
if ( x == 0 ) // "x" is the sweepStep, if zero
return; // then just return.
if ( x == 1 )
{
img.fillSprite ( BLACK ); // Clear the sprite
img.drawFastVLine ( 0, 0, gridHeight, DARKGREY ); // Draw vertical line at edge
2020-08-16 02:03:43 +08:00
for ( int y=0; y <= yGrid; y++ )
img.drawPixel ( 1, y*yDelta, DARKGREY ); // Draw the horizontal grid lines
2020-08-16 02:03:43 +08:00
}
else
{
img.setScrollRect ( 0, 0, 2,gridHeight, BLACK ); // Scroll the whole sprite
2020-08-16 02:03:43 +08:00
img.scroll ( -1, 0 );
int lastStep = x - 1;
if (( x % xDelta ) == 0 ) // Need a vertical line here
img.drawFastVLine ( 1, 0, gridHeight, DARKGREY );
2020-08-16 02:03:43 +08:00
else
for ( int y = 0; y <= yGrid; y++ )
img.drawPixel ( 1, y * yDelta, DARKGREY ); // Draw the horizontal grid lines
2020-08-16 02:03:43 +08:00
}
}
/*
* Function to work out the y coordinate on img sprite for a given RSSI value
* Takes into account the display scaling, attenuation and level offset
*/
uint16_t rssiToImgY ( uint8_t rSSI )
{
int delta = maxGrid - minGrid;
2020-08-16 02:03:43 +08:00
double y = rssiTodBm ( rSSI );
y = ( y - minGrid ) * gridHeight / delta;
2020-08-16 02:03:43 +08:00
if ( y >= gridHeight )
y = gridHeight-1;
2020-08-16 02:03:43 +08:00
if ( y < 0 )
y = 0;
return gridHeight - 1 - (int) y;
2020-08-16 02:03:43 +08:00
}
/*
* Function to convert rSSi to dBm
*/
double rssiTodBm ( uint8_t rSSI )
{
return ( rSSI / 2.0 + dBadjust );
2020-08-16 02:03:43 +08:00
}
/*
* Function to convert rSSi to dBm
*/
uint8_t dBmToRSSI ( double dBm )
{
return ( 2 * ( dBm - dBadjust ) );
}
2020-08-16 02:03:43 +08:00
/*
* "DisplayPoint" - Display a point on the chart.
*
* The "img" sprite is 2 pixels wide to enable the last points to be also
* plotted to make lines look good. The img sprite has been scrolled in
* "DrawCheckerBoard".
*/
void DisplayPoint ( uint8_t* data, int i, int color )
{
if ( i < 1 )
return;
int lastPoint = i - 1;
int delta = maxGrid - minGrid;
double f0 = data[i] / 2.0 + dBadjust; // Current point
f0 = ( f0 - minGrid ) * gridHeight / delta;
2020-08-16 02:03:43 +08:00
if ( f0 >= gridHeight )
f0 = gridHeight-1;
2020-08-16 02:03:43 +08:00
if ( f0 < 0 )
f0 = 0;
double f1 = data[lastPoint] / 2.0 + dBadjust; // Previous point
2020-08-16 02:03:43 +08:00
f1 = ( f1 - minGrid ) * gridHeight / delta;
2020-08-16 02:03:43 +08:00
if ( f1 >= gridHeight )
f1 = gridHeight-1;
2020-08-16 02:03:43 +08:00
if ( f1 < 0 )
f1 = 0;
int Y0 = gridHeight - 1 - (int) f0;
int Y1 = gridHeight - 1 - (int) f1;
2020-08-16 02:03:43 +08:00
img.drawLine ( 0, Y1, 1, Y0, color );
}
/*
* "DisplayGainPoint" - Added by M0WID to display a scan of the gain setting.
*/
void displayGainPoint ( unsigned char *data, int i, int color )
{
if ( i == 0 )
return;
int lastPoint = i - 1;
int delta = 50; // Scale of y axis
double f0 = ( data[i] ) ;
// Serial.printf ( "gain %f \n", f );
f0 = ( f0 ) * gridHeight / delta;
2020-08-16 02:03:43 +08:00
if ( f0 >= gridHeight )
f0 = gridHeight - 1;
2020-08-16 02:03:43 +08:00
if ( f0 < 0 )
f0 = 0;
double f1 = ( data[lastPoint] );
f1 = ( f1 ) * gridHeight / delta;
2020-08-16 02:03:43 +08:00
if ( f1 >= gridHeight )
f1 = gridHeight - 1;
2020-08-16 02:03:43 +08:00
if ( f1 < 0 )
f1 = 0;
int Y0 = gridHeight - 1 - (int) f0;
int Y1 = gridHeight - 1 - (int) f1;
2020-08-16 02:03:43 +08:00
img.drawLine ( 0, Y1, 1, Y0, color );
}
/*
* "CreateGainScale" puts the decibel values for the gain trace into a sprite for
* display at the right side of the grid.
*
* NOTE Due to temporary error in TFT_eSPI library the inverted colour is used
* so TFT_BLUE actually results in TFT_GREEN!
*/
void CreateGainScale ()
{
gainScaleSprite.deleteSprite();
gainScaleSprite.setAttribute ( PSRAM_ENABLE, false );
gainScaleSprite.setColorDepth ( 16 ); // Using 16 bit (RGB565) colors
gainScaleSprite.createSprite(CHAR_WIDTH * 2, gridHeight);
2020-08-16 02:03:43 +08:00
gainScaleSprite.fillSprite(BLACK);
gainScaleSprite.setPivot( 0, 0 );
gainScaleSprite.setTextSize ( 1 );
gainScaleSprite.setTextColor ( TFT_BLUE ); // Draw text (transparent background) GAIN_COLOR
int w = gainScaleSprite.width();
// Serial.printf("Create gain scale - get sprite width %i \n", w);
if ( w != CHAR_WIDTH * 2 )
{
Serial.println ( "Error - no gain scale sprite" );
return;
}
for ( int i = 0; i < 5; i++ )
{
gainScaleSprite.setCursor ( 0, 2 + ( yDelta * i * 2 ));
2020-08-16 02:03:43 +08:00
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;
}
}
2020-08-16 02:03:43 +08:00
/*
* "ledcAnalogWrite" - Arduino like analogWrite used for PWM control of backlight.
*
* "value" has to be between 0 and valueMax
*/
void ledcAnalogWrite ( uint8_t channel, uint32_t value, uint32_t valueMax = 255 )
{
if ( value > valueMax )
value = valueMax;
uint32_t duty = ( 8191 / valueMax ) * value; // Calculate duty, 8191 from 2 ^ 13 - 1
ledcWrite ( channel, duty ); // write duty to LEDC
}
/*
* "setUpLEDC" - Sets up the LEDC PWM for the backlight.
*
* "TFT_BL" is defined in the User_setup.h file in the "TFT_eSPI" library
* If BACKLIGHT_LEVEL is not defined then your board uses a resistor instead
*/
#ifdef BACKLIGHT_LEVEL
void setUpLEDC ()
{
ledcSetup ( LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT );
ledcAttachPin ( TFT_LED, LEDC_CHANNEL_0 );
ledcAnalogWrite ( LEDC_CHANNEL_0, BACKLIGHT_LEVEL );
}
#endif
/*
* "TouchCalibrate" - Runs the touchscreen calibration sequence using the
* procedure in the "TFT_eSPI" library.
*
* This used to be called from "setup" by removing the comment from the
* function call. That can still be done, however, it is now also invoked
* from the "CONFIG" menu.
*
* The calibration constants are saved to the flash memory and recalled at
* startup, so it need only be done once.
*/
void TouchCalibrate ()
{
uint8_t calDataOK = 0;
tft.fillScreen ( BLACK );
tft.setCursor ( 20, 0 );
tft.setTextFont ( 2 );
tft.setTextSize ( 1 );
tft.setTextColor ( WHITE, BLACK );
tft.println ( "Touch corners as indicated" );
tft.setTextFont ( 1 );
tft.println ();
tft.calibrateTouch ( config.touch_cal, MAGENTA, BLACK, 15 );
Serial.print ( "\n\n// New calibration data: { " );
for ( uint8_t i = 0; i < 5; i++ )
{
Serial.print ( config.touch_cal[i] );
if ( i < 4 )
Serial.print ( ", " );
}
Serial.println ( " };\n\n" );
WriteConfig(); // Save the new calibration data to flash
tft.fillScreen ( BLACK );
tft.setTextColor ( GREEN, BLACK);
tft.println ( "Calibration complete!" );
tft.println ( "Calibration values sent to Serialport\nand saved to flash." );
delay ( 3000 );
}
/*
* Print out the files in the SPIFFS (SPI Fllat File System)
* This requires some files to be loaded into the ESP32's SPIFFS file system
* before the capability can be used.
*
* Conditionalized on whether or not WiFi is in use:
*/
void printSPIFFS()
{
char fileName[25];
char fileSize[10];
Serial.println ( "Contents of SPIFFS:" );
fs::File root = SPIFFS.open ( "/" ); // Open the root object
fs::File file = root.openNextFile (); // Open the 1st file
while ( file ) // Loop through the files
{
if ( file.isDirectory () ) // If the file is a directory
{
strcpy ( fileName, file.name () );
Serial.printf ( " DIR : %-s\n", fileName );
}
else // File is a regular file
{
strcpy ( fileName, file.name() );
itoa ( file.size(), fileSize, 10 );
Serial.printf ( " FILE: %-25s%10s\n", fileName, fileSize );
}
file = root.openNextFile(); // Move to the next file
}
}