From fa55cb2fd09bb35ec980849e7afcd53f41ab0ea8 Mon Sep 17 00:00:00 2001 From: Egor Shitikov Date: Tue, 17 Sep 2024 10:17:25 -0700 Subject: [PATCH] dB output --- gps_src/main.cpp | 126 +++++++++++++++++++++++++++++++++++++++++++++ include/ui.h | 1 + platformio.ini | 2 + src/main.cpp | 100 +++++++++++++++++++++++++---------- src/ui.cpp | 10 +++- trans_src/main.cpp | 47 +++++++++++------ 6 files changed, 241 insertions(+), 45 deletions(-) create mode 100644 gps_src/main.cpp diff --git a/gps_src/main.cpp b/gps_src/main.cpp new file mode 100644 index 0000000..0ea23d7 --- /dev/null +++ b/gps_src/main.cpp @@ -0,0 +1,126 @@ +#include +#include + +// Objects for GNSS parsing and serial communication +TinyGPSPlus gps; +HardwareSerial GNSSSerial(2); + +// Pin definitions for GNSS module communication +const int GNSS_RXPin = 34; +const int GNSS_TXPin = 33; +const int GNSS_RSTPin = 35; // There is a function built for this in the example below- + // currently it isn't used +const int GNSS_PPS_Pin = 36; + +// Flags for PPS handling and synchronization status +volatile bool ppsFlag = false; +volatile bool initialSyncDone = false; + +// Timestamp for the last valid GNSS data received +unsigned long lastGNSSDataMillis = 0; + +void setup() +{ + // Initialize serial communication for debugging + USBSerial.begin(115200); + while (!USBSerial) + ; + + // Start GNSS module communication + GNSSSerial.begin(115200, SERIAL_8N1, GNSS_TXPin, GNSS_RXPin); + while (!GNSSSerial) + ; + + // Configure GNSS reset pin + pinMode(GNSS_RSTPin, OUTPUT); + digitalWrite(GNSS_RSTPin, HIGH); + + // Set up PPS pin and attach an interrupt handler + pinMode(GNSS_PPS_Pin, INPUT); + attachInterrupt(digitalPinToInterrupt(GNSS_PPS_Pin), ppsInterrupt, RISING); + + // Short delay for GNSS module initialization + delay(1000); +} + +void loop() +{ + // Process incoming GNSS data + while (GNSSSerial.available()) + { + if (gps.encode(GNSSSerial.read())) + { + // Update the timestamp when valid GNSS data is received + lastGNSSDataMillis = millis(); + displayGNSSData(); // Display GNSS data for debugging + } + } + + // Perform initial synchronization using NMEA time data + if (!initialSyncDone && gps.date.isValid() && gps.time.isValid()) + { + setSystemTime(); + initialSyncDone = true; + USBSerial.println("Initial time synchronization done using NMEA data."); + } + + // Disable interrupts to safely check and reset the PPS flag + noInterrupts(); + if (ppsFlag) + { + fineTuneSystemTime(); // Adjust system time based on the PPS pulse + ppsFlag = false; + } + // Re-enable interrupts + interrupts(); + + // Check if GNSS data has been absent for more than a minute + if (millis() - lastGNSSDataMillis > 60000) + { + USBSerial.println("Warning: Haven't received GNSS data for more than 1 minute!"); + // Additional actions can be added here, like alerts or module resets. + } +} + +// Interrupt handler for the PPS signal +void ppsInterrupt() { ppsFlag = true; } + +// Function to set system time using GNSS data +void setSystemTime() +{ + struct tm timeinfo; + timeinfo.tm_year = gps.date.year() - 1900; + timeinfo.tm_mon = gps.date.month() - 1; + timeinfo.tm_mday = gps.date.day(); + timeinfo.tm_hour = gps.time.hour(); + timeinfo.tm_min = gps.time.minute(); + timeinfo.tm_sec = gps.time.second(); + time_t t = mktime(&timeinfo); + + timeval tv = {t, 0}; + settimeofday(&tv, NULL); // Update system time +} + +// Function to fine-tune system time using the PPS pulse +void fineTuneSystemTime() +{ + timeval tv; + gettimeofday(&tv, NULL); + tv.tv_usec = 0; // Reset microseconds to zero + settimeofday(&tv, NULL); // Update system time + USBSerial.println("System time fine-tuned using PPS signal."); +} + +// Debugging function to display GNSS data +void displayGNSSData() +{ + USBSerial.print("Latitude: "); + USBSerial.println(gps.location.lat(), 6); + USBSerial.print("Longitude: "); + USBSerial.println(gps.location.lng(), 6); + USBSerial.print("Altitude: "); + USBSerial.println(gps.altitude.meters()); + USBSerial.print("Speed: "); + USBSerial.println(gps.speed.kmph()); + USBSerial.println("-----------------------------"); +} diff --git a/include/ui.h b/include/ui.h index 2ca7525..2f18a7b 100644 --- a/include/ui.h +++ b/include/ui.h @@ -40,4 +40,5 @@ extern void UI_Init(SSD1306Wire *); extern void UI_displayDecorate(int, int, bool); extern void UI_setLedFlag(bool); extern void UI_clearPlotter(void); +extern void UI_clearTopStatus(void); extern void UI_drawCursor(int16_t); diff --git a/platformio.ini b/platformio.ini index 24ece4d..26992cb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,6 +15,8 @@ ; src_dir = eink_src ; for env:heltec_wifi_lora_32_V3 ; src_dir = src ;;Default +; for heltec_wifi_lora_32_V3-test-signal-generator +; src_dir = trans_src [env:heltec_wifi_lora_32_V3] platform = espressif32 diff --git a/src/main.cpp b/src/main.cpp index 45c1419..36667a6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -194,12 +194,15 @@ bool ANIMATED_RELOAD = false; #define FILTER_SPECTRUM_RESULTS true #define FILTER_SAMPLES_MIN constexpr bool DRAW_DETECTION_TICKS = true; - +int16_t max_x_rssi[STEPS] = {999}; +int16_t max_x_window[STEPS / 14] = {999}; +int x_window = 0; +constexpr int WINDOW_SIZE = 15; // Number of samples for each frequency scan. Fewer samples = better temporal resolution. // if more than 100 it can freeze #define SAMPLES 35 //(scan time = 1294) // number of samples for RSSI method -#define SAMPLES_RSSI 20 // 21 // +#define SAMPLES_RSSI 12 // 21 // #define RANGE (int)(FREQ_END - FREQ_BEGIN) @@ -217,10 +220,9 @@ uint64_t median_frequency = FREQ_BEGIN + FREQ_END - FREQ_BEGIN / 2; // #define DISABLE_PLOT_CHART false // unused // Array to store the scan results -uint16_t result[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE]; -uint16_t result_display_set[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE]; -uint16_t result_detections[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE]; -uint16_t filtered_result[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE]; +int16_t result[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE]; + +bool filtered_result[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE]; int max_bins_array_value[MAX_POWER_LEVELS]; int max_step_range = 32; @@ -235,6 +237,7 @@ bool first_run, new_pixel, detected_x = false; // drone detection flag bool detected = false; uint64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL; +uint64_t show_db_after = 80; uint64_t drone_detected_frequency_start = 0; uint64_t drone_detected_frequency_end = 0; uint64_t detection_count = 0; @@ -610,11 +613,14 @@ bool buttonPressHandler(float freq) #endif ) { + // Print Curent frequency once + if (button_pressed_counter == 0) + { + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.drawString(128 / 2, 0, String(freq)); + display.display(); + } delay(10); - // Print Curent frequency - display.setTextAlignment(TEXT_ALIGN_CENTER); - display.drawString(128 / 2, 0, String(freq)); - display.display(); button_pressed_counter++; if (button_pressed_counter > 150) { @@ -663,21 +669,28 @@ bool buttonPressHandler(float freq) return true; } -void drone_sound_alarm(int drone_detection_level, int detection_count) +void drone_sound_alarm(int drone_detection_level, int detection_count, + int tone_freq_db = 205) { // If level is set to sensitive, // start beeping every 10th frequency and shorter // it improves performance less short beep delays... if (drone_detection_level <= 25) { + + if (tone_freq_db != 205) + { + tone_freq_db = 285 - tone_freq_db; + } + if (detection_count == 1 && SOUND_ON) { - tone(BUZZER_PIN, 205, + tone(BUZZER_PIN, tone_freq_db, 10); // same action ??? but first time } if (detection_count % 5 == 0 && SOUND_ON) { - tone(BUZZER_PIN, 205, + tone(BUZZER_PIN, tone_freq_db, 10); // same action ??? but every 5th time } } @@ -775,6 +788,7 @@ void loop(void) { // clear the scan plot rectangle UI_clearPlotter(); + UI_clearTopStatus(); } // do the scan @@ -934,6 +948,7 @@ void loop(void) for (int r = 0; r < SAMPLES_RSSI; r++) { rssi = radio.getRSSI(false); + int abs_rssi = abs(rssi); // ToDO: check if 4 is correct value for 33 power bins // Now we have more space because we are ignoring low dB values // we can / 3 default 4 @@ -941,12 +956,12 @@ void loop(void) { result_index = /// still not clear formula but it works - uint8_t(abs(rssi) / 4); + uint8_t(abs_rssi / 4); } else if (RSSI_OUTPUT_FORMULA == 2) { // I like this formula better - result_index = uint8_t(abs(rssi) / 2) - 22; + result_index = uint8_t(abs_rssi / 2) - 22; } if (result_index >= RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE) { @@ -960,11 +975,15 @@ void loop(void) // avoid buffer overflow if (result_index < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE) { - // Saving max ABS value of rssi. dB is negative so smaller is - // bigger - if (result[result_index] == 0 || result[result_index] > abs(rssi)) + // Saving max ABS value of RSSI. dB is negative, so smaller + // absolute value represents stronger signal. + if (result[result_index] == 0 || result[result_index] > abs_rssi) { - result[result_index] = abs(rssi); + result[result_index] = abs_rssi; + } + if (max_x_rssi[display_x] > abs_rssi) + { + max_x_rssi[display_x] = abs_rssi; } } else @@ -1024,7 +1043,7 @@ void loop(void) { // do not process 'first' and 'last' row to avoid out of index // access. - if ((y > 0) && (y < (RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE - 1))) + if ((y > 0) && (y < (RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE - 2))) { if (((result[y + 1] != 0) && (result[y + 2] != 0)) || (result[y - 1] != 0)) @@ -1040,12 +1059,23 @@ void loop(void) #endif } } - } // not filtering if samples == 1 + } // not filtering if samples == 1 because it will be filtered else if (result[y] > 0 && samples == 1) { filtered_result[y] = 1; } - + // calculating max window x RSSI after filters + x_window = (int)(display_x / WINDOW_SIZE); + int abs_result = abs(result[y]); + if (filtered_result[y] == 1 && result[y] != 0 && result[y] != 1 && + max_x_window[x_window] > abs_result) + { + max_x_window[x_window] = abs_result; +#ifdef PRINT_DEBUG + Serial.println("MAX x window: " + String(x_window) + " " + + String(abs_result)); +#endif + } #endif // check if we should alarm about a drone presence if ((filtered_result[y] == 1) // we have some data and @@ -1076,18 +1106,21 @@ void loop(void) drone_detected_frequency_end = freq; if (SOUND_ON == true) { - drone_sound_alarm(drone_detection_level, detection_count); + drone_sound_alarm(drone_detection_level, detection_count, + max_rssi_x * 2); } if (DRAW_DETECTION_TICKS == true) { - // draw vertical line on top of display for "drone detected" - // frequencies +// draw vertical line on top of display for "drone detected" +// frequencies +#ifdef METHOD_SPECTRAL if (!detected_y[display_x]) { display.drawLine(display_x, 1, display_x, 4); detected_y[display_x] = true; } +#endif } } #if (WATERFALL_ENABLED == true) @@ -1114,7 +1147,7 @@ void loop(void) // MAx bin Value not RSSI max_rssi_x = y; } - // Set signal level pixel + // Set MAIN signal level pixel if (y < MAX_POWER_LEVELS - START_LOW) { display.setPixel(display_x, y + START_LOW); @@ -1232,6 +1265,19 @@ void loop(void) display.setColor(WHITE); } #endif + +#ifdef METHOD_RSSI + // Printing Max Window DB. + for (int x2 = 0; x2 < STEPS / WINDOW_SIZE; x2++) + { + if (max_x_window[x2] < show_db_after && max_x_window[x2] != 0) + { + display.drawString(x2 * WINDOW_SIZE + WINDOW_SIZE, 0, + "-" + String(max_x_window[x2])); + } + max_x_window[x2] = 999; + } +#endif // Render display data here display.display(); #ifdef OSD_ENABLED @@ -1251,7 +1297,7 @@ void loop(void) #endif } #ifdef PRINT_DEBUG - // Serial.println("----"); +// Serial.println("----"); #endif loop_time = millis() - loop_start; diff --git a/src/ui.cpp b/src/ui.cpp index 48a7bf1..94342b7 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -80,7 +80,15 @@ void UI_clearPlotter(void) { // clear the scan plot rectangle (top part) display_instance->setColor(BLACK); - display_instance->fillRect(0, 0, STEPS, HEIGHT); + display_instance->fillRect(0, 10, STEPS, HEIGHT - 10); + display_instance->setColor(WHITE); +} + +void UI_clearTopStatus(void) +{ + // clear the scan plot rectangle (top part) + display_instance->setColor(BLACK); + display_instance->fillRect(0, 0, STEPS, 10); display_instance->setColor(WHITE); } diff --git a/trans_src/main.cpp b/trans_src/main.cpp index 2010f48..e40a4c2 100644 --- a/trans_src/main.cpp +++ b/trans_src/main.cpp @@ -8,6 +8,7 @@ * This works on the stick, but the output on the screen gets cut off. */ +#include // Turns the 'PRG' button into the power button, long press is off #define HELTEC_POWER_BUTTON // must be before "#include " #include @@ -15,11 +16,11 @@ // Pause between transmited packets in mseconds. // Set to zero to only transmit a packet when pressing the user button // Will not exceed 1% duty cycle, even if you set a lower value. -#define PAUSE 20 +#define PAUSE 10 // Frequency in MHz. Keep the decimal point to designate float. // Check your own rules and regulations to see what is legal where you are. -#define FREQUENCY 866.3 // for Europe +#define FREQUENCY 915 // for Europe // #define FREQUENCY 905.2 // for US // LoRa bandwidth. Keep the decimal point to designate float. @@ -29,13 +30,13 @@ // Number from 5 to 12. Higher means slower but higher "processor gain", // meaning (in nutshell) longer range and more robust against interference. -#define SPREADING_FACTOR 9 +#define SPREADING_FACTOR 7 // Transmit power in dBm. 0 dBm = 1 mW, enough for tabletop-testing. This value can be // set anywhere between -9 dBm (0.125 mW) to 22 dBm (158 mW). Note that the maximum ERP // (which is what your antenna maximally radiates) on the EU ISM band is 25 mW, and that // transmissting without an antenna can damage your hardware. -#define TRANSMIT_POWER -9 +#define TRANSMIT_POWER 22 String rxdata; volatile bool rxFlag = false; @@ -67,35 +68,41 @@ void setup() RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF)); } +int FHSS = 0.25; +float FHSS_counter = -10; void loop() { heltec_loop(); - bool tx_legal = millis() > last_tx + minimum_pause; + bool tx_legal = true; // millis() > last_tx + minimum_pause; + // Emulate frequency hopping spread spectrum (FHSS) is a method of transmitting radio + // signals by rapidly switching the carrier between different frequency channels. + float fr = (float)(FREQUENCY + (float)(FHSS_counter)); + RADIOLIB_OR_HALT(radio.setFrequency(fr, false)); // Transmit a packet every PAUSE seconds or when the button is pressed if ((PAUSE && tx_legal && millis() - last_tx > (PAUSE)) || button.isSingleClick()) { - // In case of button click, tell user to wait - if (!tx_legal) + if (button.isSingleClick()) { - both.printf("Legal limit, wait %i sec.\n", - (int)((minimum_pause - (millis() - last_tx)) / 1000) + 1); - return; + fr = 1000; + RADIOLIB_OR_HALT(radio.setFrequency(fr, false)); } - both.printf("TX [%s] ", String(counter).c_str()); + // In case of button click, tell user to wait + display.printf("TX[%s]", String(counter).c_str()); radio.clearDio1Action(); heltec_led(50); // 50% brightness is plenty for this LED tx_time = millis(); - RADIOLIB(radio.transmit(String(counter++).c_str())); + RADIOLIB(radio.transmit(String("Putin Huylo!!! LA-LA-LA-LA").c_str())); tx_time = millis() - tx_time; heltec_led(0); if (_radiolib_status == RADIOLIB_ERR_NONE) { - both.printf("OK (%i ms)\n", (int)tx_time); + display.printf("OK(%ims)/%.2fMhz\n", (int)tx_time, fr); } else { - both.printf("fail (%i)\n", _radiolib_status); + display.printf("fail (%i)\n", _radiolib_status); + heltec_delay(100); } // Maximum 1% duty cycle minimum_pause = tx_time * 100; @@ -111,10 +118,16 @@ void loop() radio.readData(rxdata); if (_radiolib_status == RADIOLIB_ERR_NONE) { - both.printf("RX [%s]\n", rxdata.c_str()); - both.printf(" RSSI: %.2f dBm\n", radio.getRSSI()); - both.printf(" SNR: %.2f dB\n", radio.getSNR()); + display.printf("RX [%s]\n", rxdata.c_str()); + display.printf(" RSSI: %.2f dBm\n", radio.getRSSI()); + display.printf(" SNR: %.2f dB\n", radio.getSNR()); } RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF)); } + FHSS_counter += 0.250; + counter++; + if (FHSS_counter > 20) + { + FHSS_counter = -10; + } }