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-18 05:50:53 +08:00
|
|
|
*
|
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.
|
2020-08-18 05:50:53 +08:00
|
|
|
* 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
|
|
|
*
|
2020-08-17 05:24:08 +08:00
|
|
|
* Version 3.0f Changes by M0WID
|
2020-10-07 06:06:59 +08:00
|
|
|
*
|
|
|
|
* Version 0.03
|
2020-08-17 05:24:08 +08:00
|
|
|
* 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
|
2020-09-03 06:22:08 +08:00
|
|
|
* Put onto github - lets see how it works! Version numbering changed - still in beta!
|
2020-08-17 05:24:08 +08:00
|
|
|
*
|
2020-10-07 06:06:59 +08:00
|
|
|
* 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-10-09 06:08:59 +08:00
|
|
|
*
|
|
|
|
* Version 0.11 (pending)
|
|
|
|
* Fix scaling for track gen frequency on web
|
|
|
|
* Axis Y2 on web chart is hidden if relevant traces not enabled
|
|
|
|
* Add self-calibrate
|
|
|
|
*
|
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
|
2020-08-17 05:24:08 +08:00
|
|
|
#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
|
2020-08-17 05:24:08 +08:00
|
|
|
* 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)
|
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
#ifdef TG_IF_INSTALLED
|
2020-08-16 02:03:43 +08:00
|
|
|
Si4432 tg_if ( vspi, SI_TG_IF_CS, TGIF_4432 ); // Create object for the tracking generator IF SI4432
|
|
|
|
#endif
|
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
#ifdef TG_LO_INSTALLED
|
2020-08-16 02:03:43 +08:00
|
|
|
Si4432 tg_lo ( vspi, SI_TG_LO_CS, TGLO_4432 ); // Create object for the tracking generator LO SI4432
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Global definitions:
|
|
|
|
*/
|
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
bool tgIF_OK; // true if the tracking generator IF SI4432 is present and initialised OK
|
|
|
|
bool tgLO_OK; // true if the tracking generator LO SI4432 is present and initialised OK
|
2020-08-16 02:03:43 +08:00
|
|
|
|
|
|
|
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;
|
2020-09-09 06:17:41 +08:00
|
|
|
uint16_t websocketFailCount;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
|
|
|
#ifdef USE_WIFI
|
|
|
|
// Json document buffers
|
2020-09-15 04:49:22 +08:00
|
|
|
//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
|
|
|
|
2020-09-15 04:49:22 +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
|
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
/*
|
|
|
|
* 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;
|
2020-08-21 05:27:55 +08:00
|
|
|
int16_t maxGrid;
|
|
|
|
int16_t minGrid;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-17 05:24:08 +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
|
2020-09-08 06:31:06 +08:00
|
|
|
const char *modeText[] = { "SALo", "SAHi", "SGLo", "SGHi", "IFSw", "0SpL", "0SpH", "BScp", "RXSw" }; // For mode display
|
2020-08-17 05:24:08 +08:00
|
|
|
|
2020-08-16 02:03:43 +08:00
|
|
|
|
|
|
|
float bandwidth; // The current bandwidth (not * 10)
|
2020-08-21 05:27:55 +08:00
|
|
|
unsigned long delaytime = 2000; // delay time to allow SI4432 filters to settle
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-17 05:24:08 +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
|
2020-09-05 06:54:52 +08:00
|
|
|
double dBadjust; // Sum of attenuation, external gain, calibration offset and RBW correction
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-10-09 06:08:59 +08:00
|
|
|
uint16_t bpfCalibrate; // set true if a SI4432 bandpass filter calibration run is taking place
|
|
|
|
uint16_t bpfCount; // no of bandpass filters available
|
|
|
|
|
2020-08-21 05:27:55 +08:00
|
|
|
/*
|
2020-10-09 06:08:59 +08:00
|
|
|
* Variables for offset frequency tuning (used in Bandscope mode)
|
2020-08-21 05:27:55 +08:00
|
|
|
*/
|
|
|
|
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
|
2020-08-22 05:02:22 +08:00
|
|
|
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 ();
|
2020-09-03 06:22:08 +08:00
|
|
|
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;
|
2020-08-22 05:02:22 +08:00
|
|
|
uint32_t sigFreq_IF = 15000000; // 15 Mhz reference
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-22 05:02:22 +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
|
2020-08-17 05:24:08 +08:00
|
|
|
* here and in the "TinySA_wifi" modules:
|
2020-08-16 02:03:43 +08:00
|
|
|
*/
|
|
|
|
|
2020-08-17 05:24:08 +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)
|
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
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;
|
2020-09-03 06:22:08 +08:00
|
|
|
static int requiredRBW10 = 0;
|
|
|
|
static int old_requiredRBW10 = -1;
|
2020-08-17 05:24:08 +08:00
|
|
|
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;
|
|
|
|
|
2020-09-29 05:44:47 +08:00
|
|
|
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");
|
|
|
|
|
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the CS pins normally used for the SI4432 to high, so if present they do not interfere
|
|
|
|
* with the bus signals
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
pinMode (SI_TG_LO_CS, OUTPUT);
|
|
|
|
digitalWrite(SI_TG_LO_CS, HIGH); // make sure the module doesn't get selected
|
|
|
|
|
|
|
|
pinMode (SI_TG_IF_CS, OUTPUT);
|
|
|
|
digitalWrite(SI_TG_IF_CS, HIGH); // make sure the module doesn't get selected
|
|
|
|
|
|
|
|
|
2020-08-16 02:03:43 +08:00
|
|
|
/*
|
|
|
|
* Initialize the SI4432 for RX and LO(TX)
|
|
|
|
*/
|
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
tft.setCursor(0,120);
|
2020-08-16 02:03:43 +08:00
|
|
|
// 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 );
|
2020-10-09 06:08:59 +08:00
|
|
|
bpfCount = rcvr.GetBandpassFilterCount(); // no of RBW filters available
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
|
2020-08-16 02:03:43 +08:00
|
|
|
// If the tracking generator IF SI4432 is defined, then check to see if it is connected
|
|
|
|
tgIF_OK = false;
|
2020-09-29 05:44:47 +08:00
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
#ifdef TG_IF_INSTALLED
|
|
|
|
|
|
|
|
tft.setCursor(0,150);
|
|
|
|
tft.println ( "Initializing track gen IF SI4432" );
|
|
|
|
|
|
|
|
tgIF_OK = tg_if.Init ( config.tgIF_capacitance );
|
|
|
|
if ( tgIF_OK )
|
2020-08-16 02:03:43 +08:00
|
|
|
{
|
|
|
|
tft.println ("Tracking IF initialised");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DisplayError ( ERR_WARN,
|
|
|
|
"Track gen IF SI4432 failed",
|
|
|
|
"to initialise", NULL, NULL );
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If the tracking generator LO SI4432 is defined, then check to see if it is connected
|
|
|
|
tgLO_OK = false;
|
2020-09-29 05:44:47 +08:00
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
#ifdef TG_LO_INSTALLED
|
2020-10-07 06:06:59 +08:00
|
|
|
|
|
|
|
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
|
2020-10-10 04:14:58 +08:00
|
|
|
{
|
2020-08-16 02:03:43 +08:00
|
|
|
DisplayError ( ERR_WARN,
|
|
|
|
"Track gen LO SI4432 failed",
|
|
|
|
"to initialise", NULL, NULL );
|
2020-10-10 04:14:58 +08:00
|
|
|
}
|
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
|
2020-08-17 05:24:08 +08:00
|
|
|
// 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");
|
|
|
|
|
2020-10-07 06:06:59 +08:00
|
|
|
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;
|
|
|
|
|
2020-08-22 05:02:22 +08:00
|
|
|
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;
|
|
|
|
|
2020-08-22 05:02:22 +08:00
|
|
|
case BANDSCOPE:
|
|
|
|
initBandscope();
|
|
|
|
break;
|
|
|
|
|
2020-08-16 02:03:43 +08:00
|
|
|
case IF_SWEEP:
|
|
|
|
initIF_Sweep();
|
|
|
|
break;
|
|
|
|
|
2020-08-22 05:02:22 +08:00
|
|
|
case RX_SWEEP:
|
|
|
|
initRX_Sweep();
|
2020-08-16 02:03:43 +08:00
|
|
|
break;
|
|
|
|
|
2020-08-22 05:02:22 +08:00
|
|
|
|
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;
|
|
|
|
|
2020-08-22 05:02:22 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2020-08-22 05:02:22 +08:00
|
|
|
* 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
|
2020-08-17 05:24:08 +08:00
|
|
|
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;
|
2020-09-03 06:22:08 +08:00
|
|
|
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 );
|
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
#ifdef TG_IF_INSTALLED
|
2020-08-16 02:03:43 +08:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
#ifdef TG_LO_INSTALLED
|
2020-08-16 02:03:43 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-15 04:49:22 +08:00
|
|
|
/*
|
|
|
|
* 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();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-10-09 06:08:59 +08:00
|
|
|
|
|
|
|
|
2020-08-16 02:03:43 +08:00
|
|
|
/*
|
|
|
|
* "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();
|
2020-08-17 05:24:08 +08:00
|
|
|
switch (tinySA_mode)
|
|
|
|
{
|
2020-08-16 05:44:19 +08:00
|
|
|
case BANDSCOPE:
|
|
|
|
DisplayBandscopeInfo();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DisplayInfo();
|
|
|
|
break;
|
2020-08-17 05:24:08 +08:00
|
|
|
|
2020-08-16 05:44:19 +08:00
|
|
|
}
|
2020-08-17 05:24:08 +08:00
|
|
|
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;
|
|
|
|
|
2020-08-22 05:02:22 +08:00
|
|
|
// 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 );
|
2020-08-21 05:27:55 +08:00
|
|
|
sSprite.printf ( "%4i", maxGrid );
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
sSprite.setCursor ( 0, gridHeight + yOrigin );
|
2020-08-21 05:27:55 +08:00
|
|
|
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
|
2020-09-03 06:22:08 +08:00
|
|
|
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" );
|
|
|
|
|
2020-10-10 04:14:58 +08:00
|
|
|
if (tgIF_OK)
|
|
|
|
{
|
|
|
|
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" );
|
|
|
|
}
|
2020-08-16 02:03:43 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2020-08-22 05:02:22 +08:00
|
|
|
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 )
|
|
|
|
{
|
2020-08-17 05:24:08 +08:00
|
|
|
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
|
2020-08-17 05:24:08 +08:00
|
|
|
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 );
|
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
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:
|
|
|
|
*/
|
2020-08-17 05:24:08 +08:00
|
|
|
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 );
|
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
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()
|
|
|
|
{
|
2020-08-17 05:24:08 +08:00
|
|
|
for ( int p=0; p<displayPoints; p++ )
|
2020-08-16 02:03:43 +08:00
|
|
|
{
|
|
|
|
DrawCheckerBoard ( p );
|
|
|
|
|
|
|
|
if ( p > 0 )
|
2020-08-17 05:24:08 +08:00
|
|
|
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.
|
|
|
|
*
|
2020-08-17 05:24:08 +08:00
|
|
|
* 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
|
2020-08-17 05:24:08 +08:00
|
|
|
img.drawFastVLine ( 0, 0, gridHeight, DARKGREY ); // Draw vertical line at edge
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-17 05:24:08 +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
|
|
|
|
{
|
2020-08-17 05:24:08 +08:00
|
|
|
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;
|
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
if (( x % xDelta ) == 0 ) // Need a vertical line here
|
|
|
|
img.drawFastVLine ( 1, 0, gridHeight, DARKGREY );
|
2020-08-16 02:03:43 +08:00
|
|
|
|
|
|
|
else
|
2020-08-17 05:24:08 +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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 )
|
|
|
|
{
|
2020-08-21 05:27:55 +08:00
|
|
|
int delta = maxGrid - minGrid;
|
2020-08-16 02:03:43 +08:00
|
|
|
double y = rssiTodBm ( rSSI );
|
|
|
|
|
2020-08-21 05:27:55 +08:00
|
|
|
y = ( y - minGrid ) * gridHeight / delta;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
if ( y >= gridHeight )
|
|
|
|
y = gridHeight-1;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
|
|
|
if ( y < 0 )
|
|
|
|
y = 0;
|
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
return gridHeight - 1 - (int) y;
|
2020-08-16 02:03:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Function to convert rSSi to dBm
|
|
|
|
*/
|
|
|
|
|
|
|
|
double rssiTodBm ( uint8_t rSSI )
|
|
|
|
{
|
2020-09-05 06:54:52 +08:00
|
|
|
return ( rSSI / 2.0 + dBadjust );
|
2020-08-16 02:03:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-08-22 05:02:22 +08:00
|
|
|
/*
|
|
|
|
* Function to convert rSSi to dBm
|
|
|
|
*/
|
|
|
|
|
|
|
|
uint8_t dBmToRSSI ( double dBm )
|
|
|
|
{
|
2020-09-05 06:54:52 +08:00
|
|
|
return ( 2 * ( dBm - dBadjust ) );
|
2020-08-22 05:02:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
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;
|
2020-08-21 05:27:55 +08:00
|
|
|
int delta = maxGrid - minGrid;
|
2020-09-05 06:54:52 +08:00
|
|
|
double f0 = data[i] / 2.0 + dBadjust; // Current point
|
2020-08-21 05:27:55 +08:00
|
|
|
f0 = ( f0 - minGrid ) * gridHeight / delta;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
if ( f0 >= gridHeight )
|
|
|
|
f0 = gridHeight-1;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
|
|
|
if ( f0 < 0 )
|
|
|
|
f0 = 0;
|
|
|
|
|
2020-09-05 06:54:52 +08:00
|
|
|
double f1 = data[lastPoint] / 2.0 + dBadjust; // Previous point
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-21 05:27:55 +08:00
|
|
|
f1 = ( f1 - minGrid ) * gridHeight / delta;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
if ( f1 >= gridHeight )
|
|
|
|
f1 = gridHeight-1;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
|
|
|
if ( f1 < 0 )
|
|
|
|
f1 = 0;
|
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
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 );
|
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
f0 = ( f0 ) * gridHeight / delta;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-17 05:24:08 +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] );
|
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
f1 = ( f1 ) * gridHeight / delta;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
if ( f1 >= gridHeight )
|
|
|
|
f1 = gridHeight - 1;
|
2020-08-16 02:03:43 +08:00
|
|
|
|
|
|
|
if ( f1 < 0 )
|
|
|
|
f1 = 0;
|
|
|
|
|
2020-08-17 05:24:08 +08:00
|
|
|
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
|
2020-08-17 05:24:08 +08:00
|
|
|
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++ )
|
|
|
|
{
|
2020-08-17 05:24:08 +08:00
|
|
|
gainScaleSprite.setCursor ( 0, 2 + ( yDelta * i * 2 ));
|
2020-08-16 02:03:43 +08:00
|
|
|
gainScaleSprite.print ( 50 - ( i * 10 ));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-22 05:02:22 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* "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
|
|
|
|
}
|
|
|
|
}
|