|
|
|
@ -1,11 +1,7 @@
|
|
|
|
|
#include <Adafruit_LIS2MDL.h> // Magnetometer (Compass) library
|
|
|
|
|
#include <Adafruit_Sensor.h> // Unified sensor library
|
|
|
|
|
#include <Wire.h> // I2C communication
|
|
|
|
|
#include <Adafruit_GFX.h> // Core graphics library for the display
|
|
|
|
|
#include <Adafruit_ST7735.h> // Specific library for the ST7735 display
|
|
|
|
|
#include <Adafruit_ST7789.h> // Specific library for the ST7789 display
|
|
|
|
|
#include <SPI.h> // SPI communication for the display
|
|
|
|
|
#include <FastLED.h> // Library to control the LED ring (WS2812)
|
|
|
|
|
#include <TinyGPS++.h> // Library to handle GPS data
|
|
|
|
|
#include <SoftwareSerial.h> // Software serial for GPS communication
|
|
|
|
|
#include <RH_RF95.h> // RadioHead Library(LoRa)
|
|
|
|
@ -14,17 +10,13 @@
|
|
|
|
|
|
|
|
|
|
//GLOBAL VARS/OBJS
|
|
|
|
|
|
|
|
|
|
// MAGNETOMETER (LIS2MDL) CONFIGURATION
|
|
|
|
|
Adafruit_LIS2MDL lis2mdl = Adafruit_LIS2MDL(12345); // Create magnetometer object
|
|
|
|
|
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); // Initialize display
|
|
|
|
|
CRGB leds[NUM_LEDS]; // Array to hold LED colors
|
|
|
|
|
SoftwareSerial gpsSerial(GPS_RX_PIN, GPS_TX_PIN); // Software serial for GPS
|
|
|
|
|
TinyGPSPlus gps; // TinyGPS++ object to process GPS data
|
|
|
|
|
RH_RF95 rf95(RFM9X_CS, RFM9X_INT); // Radio instance.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Variables
|
|
|
|
|
static int prevHeading = -1; // Store previous heading to avoid frequent updates
|
|
|
|
|
unsigned long lastUpdate = 0; // Time tracking for updates
|
|
|
|
|
const int updateInterval = 500; // Update interval in milliseconds (500ms = 0.5s)
|
|
|
|
|
|
|
|
|
@ -38,6 +30,7 @@ typedef struct MSG{
|
|
|
|
|
double longitude;
|
|
|
|
|
}MSG;
|
|
|
|
|
|
|
|
|
|
const uint8_t MSG_Size = sizeof(MSG);
|
|
|
|
|
|
|
|
|
|
enum result {
|
|
|
|
|
Ok = 0,
|
|
|
|
@ -55,14 +48,14 @@ MSG msg_in;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MSG_Print(MSG *msg) {
|
|
|
|
|
Serial.print("ID: ");
|
|
|
|
|
Serial.println(msg->id);
|
|
|
|
|
//Serial.print("ID: ");
|
|
|
|
|
//Serial.println(msg->id);
|
|
|
|
|
|
|
|
|
|
Serial.print("Latitude: ");
|
|
|
|
|
Serial.println(msg->latitude);
|
|
|
|
|
//Serial.print("Latitude: ");
|
|
|
|
|
//Serial.println(msg->latitude);
|
|
|
|
|
|
|
|
|
|
Serial.print("Longitude: ");
|
|
|
|
|
Serial.println(msg->longitude);
|
|
|
|
|
//Serial.print("Longitude: ");
|
|
|
|
|
//Serial.println(msg->longitude);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Radio_Reset(void) {
|
|
|
|
@ -73,7 +66,7 @@ void Radio_Reset(void) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t Radio_Setup(void) {
|
|
|
|
|
Serial.println("Radio_Setup()");
|
|
|
|
|
//Serial.println("Radio_Setup()");
|
|
|
|
|
|
|
|
|
|
pinMode(RFM9X_RST, OUTPUT);
|
|
|
|
|
digitalWrite(RFM9X_RST, HIGH);
|
|
|
|
@ -81,21 +74,21 @@ uint8_t Radio_Setup(void) {
|
|
|
|
|
Radio_Reset();
|
|
|
|
|
|
|
|
|
|
while (!rf95.init()) {
|
|
|
|
|
Serial.println("Radio_Setup(): Failed to initialize");
|
|
|
|
|
Serial.println("Check SPI connections!");
|
|
|
|
|
//Serial.println("Radio_Setup(): Failed to initialize");
|
|
|
|
|
//Serial.println("Check SPI connections!");
|
|
|
|
|
return Err;
|
|
|
|
|
}
|
|
|
|
|
Serial.println("LoRa radio init OK!");
|
|
|
|
|
//Serial.println("LoRa radio init OK!");
|
|
|
|
|
return Ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t Radio_Configure(void) {
|
|
|
|
|
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
|
|
|
|
if(!rf95.setFrequency(RF95_FREQ)) {
|
|
|
|
|
Serial.println("Radio_Configure(): failed to set frequency");
|
|
|
|
|
if(!rf95.setFrequency(RF9X_FREQ)) {
|
|
|
|
|
//Serial.println("Radio_Configure(): failed to set frequency");
|
|
|
|
|
return Err;
|
|
|
|
|
}
|
|
|
|
|
Serial.print("Set Freq to: "); Serial.println(RF9X_FREQ);
|
|
|
|
|
//Serial.println(RF9X_FREQ);
|
|
|
|
|
|
|
|
|
|
rf95.setTxPower(RF9X_MIN_DB, false);
|
|
|
|
|
|
|
|
|
@ -103,17 +96,17 @@ uint8_t Radio_Configure(void) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t Radio_SendMsg(MSG *msg) {
|
|
|
|
|
Serial.println("Radio_SendMsg()");
|
|
|
|
|
//Serial.println("Radio_SendMsg()");
|
|
|
|
|
|
|
|
|
|
Serial.println("Sending Message: ");
|
|
|
|
|
Serial.print("Message size: ");
|
|
|
|
|
Serial.println(sizeof(MSG));
|
|
|
|
|
//Serial.println("Sending Message: ");
|
|
|
|
|
//Serial.print("Message size: ");
|
|
|
|
|
//Serial.println(sizeof(MSG));
|
|
|
|
|
|
|
|
|
|
MSG_Print(msg);
|
|
|
|
|
|
|
|
|
|
rf95.send((uint8_t *)msg, sizeof(MSG));
|
|
|
|
|
|
|
|
|
|
Serial.println("waiting for packet sent...");
|
|
|
|
|
//Serial.println("waiting for packet sent...");
|
|
|
|
|
delay(10);
|
|
|
|
|
rf95.waitPacketSent();
|
|
|
|
|
|
|
|
|
@ -121,13 +114,13 @@ uint8_t Radio_SendMsg(MSG *msg) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t Radio_CheckForMsg(MSG *msg) {
|
|
|
|
|
Serial.println("Radio_CheckForMsg()");
|
|
|
|
|
//Serial.println("Radio_CheckForMsg()");
|
|
|
|
|
|
|
|
|
|
//Now we wait for an packet.
|
|
|
|
|
if (rf95.waitAvailableTimeout(1000)) {
|
|
|
|
|
// Should be a reply message for us now
|
|
|
|
|
if (rf95.recv(buffer, sizeof(MSG))) {
|
|
|
|
|
Serial.print("Received reply: ");
|
|
|
|
|
if (rf95.recv(buffer, (uint8_t *) &MSG_Size)) {
|
|
|
|
|
//Serial.print("Received reply: ");
|
|
|
|
|
|
|
|
|
|
msg->id = (uint8_t) buffer[0];
|
|
|
|
|
msg->latitude = *(double *) &buffer[1];
|
|
|
|
@ -135,14 +128,14 @@ uint8_t Radio_CheckForMsg(MSG *msg) {
|
|
|
|
|
|
|
|
|
|
MSG_Print(msg);
|
|
|
|
|
|
|
|
|
|
Serial.print("RSSI: ");
|
|
|
|
|
Serial.println(rf95.lastRssi(), DEC);
|
|
|
|
|
//Serial.print("RSSI: ");
|
|
|
|
|
//Serial.println(rf95.lastRssi(), DEC);
|
|
|
|
|
} else {
|
|
|
|
|
Serial.println("Receive failed");
|
|
|
|
|
//Serial.println("Receive failed");
|
|
|
|
|
return ReceiveFailed;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Serial.println("No reply");
|
|
|
|
|
//Serial.println("No reply");
|
|
|
|
|
return NoReply;
|
|
|
|
|
}
|
|
|
|
|
return Ok;
|
|
|
|
@ -156,8 +149,8 @@ void Radio_Main(MSG *msg_out, MSG *msg_in) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
Serial.print("TX Power set to:");
|
|
|
|
|
Serial.println(db);
|
|
|
|
|
//Serial.print("TX Power set to:");
|
|
|
|
|
//Serial.println(db);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -170,9 +163,9 @@ void Radio_Main(MSG *msg_out, MSG *msg_in) {
|
|
|
|
|
|
|
|
|
|
//Returns the result type and updates the global values(coordinates).
|
|
|
|
|
uint8_t GPS_GetCoordinates() {
|
|
|
|
|
Serial.println("GPS_GetCoordinates()");
|
|
|
|
|
//Serial.println("GPS_GetCoordinates()");
|
|
|
|
|
if (!gpsSerial.available()) {
|
|
|
|
|
Serial.println("INFO: GPS not availble.");
|
|
|
|
|
//Serial.println("INFO: GPS not availble.");
|
|
|
|
|
return NoReply;
|
|
|
|
|
}
|
|
|
|
|
gps.encode(gpsSerial.read()); // Decode the GPS data
|
|
|
|
@ -190,15 +183,6 @@ void setup(void) {
|
|
|
|
|
|
|
|
|
|
TFT_Init();
|
|
|
|
|
|
|
|
|
|
// Magnetometer Setup
|
|
|
|
|
if (!lis2mdl.begin()) { // Initialize the magnetometer
|
|
|
|
|
Serial.println("Error: LIS2MDL not detected. Check your wiring!");
|
|
|
|
|
while (1) delay(10); // Stop the program if the sensor isn't detected
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LED Ring Setup
|
|
|
|
|
FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
|
|
|
|
|
|
|
|
|
|
// GPS Setup
|
|
|
|
|
gpsSerial.begin(GPS_BUADRATE);
|
|
|
|
|
|
|
|
|
@ -222,61 +206,28 @@ void setup(void) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void loop() {
|
|
|
|
|
unsigned long currentTime = millis(); // Get the current time
|
|
|
|
|
//unsigned long currentTime = millis(); // Get the current time
|
|
|
|
|
|
|
|
|
|
// Magnetometer Readings
|
|
|
|
|
sensors_event_t event; // Create an event to store magnetometer readings
|
|
|
|
|
lis2mdl.getEvent(&event); // Get magnetic field data
|
|
|
|
|
int correctedX = event.magnetic.x + 18; // Apply corrections to X-axis data
|
|
|
|
|
int correctedY = event.magnetic.y + 59; // Apply corrections to Y-axis data
|
|
|
|
|
|
|
|
|
|
// Calculate the heading (bearing) from the magnetometer data
|
|
|
|
|
|
|
|
|
|
//NEED TO ADD TILT COMPENSATION
|
|
|
|
|
|
|
|
|
|
int heading = (atan2(correctedX, correctedY) * 180) / PI; // Calculate heading in degrees
|
|
|
|
|
heading = heading - 90; // Adjust to align with compass directions
|
|
|
|
|
if (heading < 0) heading += 360; // Ensure the heading is within the 0-360 range
|
|
|
|
|
|
|
|
|
|
// Update LED ring and display every 500ms to avoid flickering
|
|
|
|
|
if (currentTime - lastUpdate > updateInterval) {
|
|
|
|
|
updateLEDs(heading); // Update the LED ring based on the current heading
|
|
|
|
|
updateDisplay(); // Placeholder: Update the central TFT display with GPS data
|
|
|
|
|
lastUpdate = currentTime; // Reset the last update time
|
|
|
|
|
}
|
|
|
|
|
//Update GPS
|
|
|
|
|
GPS_GetCoordinates();
|
|
|
|
|
|
|
|
|
|
//Handle the Radio data.
|
|
|
|
|
sendGPSData();
|
|
|
|
|
receiveGPSData();
|
|
|
|
|
Radio_Main(&msg_out, &msg_in);
|
|
|
|
|
|
|
|
|
|
//Preform display update.
|
|
|
|
|
updateDisplay();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Function to update the LED ring based on heading direction
|
|
|
|
|
void updateLEDs(int heading) {
|
|
|
|
|
if (abs(heading - prevHeading) >= 10) { // Only update if the heading changes by 10 degrees or more
|
|
|
|
|
prevHeading = heading; // Save the current heading as the previous heading
|
|
|
|
|
int ledIndex = map(heading, 0, 360, 0, NUM_LEDS - 1); // Map the heading to an LED index (0-23)
|
|
|
|
|
|
|
|
|
|
// Update the LEDs in the ring
|
|
|
|
|
for (int i = 0; i < NUM_LEDS; i++) {
|
|
|
|
|
leds[i] = (i == ledIndex) ? CRGB(255, 0, 0) : CRGB::Black; // Turn on the correct LED for the heading
|
|
|
|
|
}
|
|
|
|
|
FastLED.show(); // Display the updated LED colors
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Updates the distance part of the display
|
|
|
|
|
void TFT_UpdateDistance(uint16_t meters) {
|
|
|
|
|
Serial.println("TFT_UpdateDistance()");
|
|
|
|
|
//Serial.println("TFT_UpdateDistance()");
|
|
|
|
|
tft.setTextSize(4);
|
|
|
|
|
tft.setCursor(0, 20);
|
|
|
|
|
tft.setTextColor(ST77XX_WHITE);
|
|
|
|
|
tft.setTextWrap(true);
|
|
|
|
|
tft.println(" Distance");
|
|
|
|
|
tft.print(" ");
|
|
|
|
|
tft.print(distance);
|
|
|
|
|
tft.print(meters);
|
|
|
|
|
tft.print("m");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -288,7 +239,7 @@ void TFT_UpdateDirection(uint16_t degrees) {
|
|
|
|
|
tft.setCursor(0, 150);
|
|
|
|
|
tft.setTextColor(ST77XX_WHITE);
|
|
|
|
|
tft.setTextSize(3);
|
|
|
|
|
tft.print(meters);
|
|
|
|
|
tft.print(degrees);
|
|
|
|
|
tft.println(" Degrees");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -317,20 +268,22 @@ uint8_t TFT_UpdateBatteryState(uint8_t percentage) {
|
|
|
|
|
|
|
|
|
|
// Displays the specifications of the node.
|
|
|
|
|
void TFT_DisplaySpecs(void) {
|
|
|
|
|
Serial.println("TFT_DisplaySpecs(): No data");
|
|
|
|
|
//Serial.println("TFT_DisplaySpecs(): No data");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Resets the the display
|
|
|
|
|
uint8_t TFT_Reset() {
|
|
|
|
|
Serial.println("TFT_Reset()");
|
|
|
|
|
//Serial.println("TFT_Reset()");
|
|
|
|
|
tft.fillScreen(ST77XX_BLACK); // Set the screen background to black
|
|
|
|
|
//Can add default startup logo/screen here for later.
|
|
|
|
|
|
|
|
|
|
return Ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initalizes the Screen with default startup screen.
|
|
|
|
|
uint8_t TFT_Init(void) {
|
|
|
|
|
Serial.println("TFT_Init()");
|
|
|
|
|
//Serial.println("TFT_Init()");
|
|
|
|
|
// Display Setup
|
|
|
|
|
tft.init(TFT_X, TFT_Y); // Initialize the display with a resolution of 240x280 pixels
|
|
|
|
|
tft.fillScreen(ST77XX_BLACK); // Set the screen background to black
|
|
|
|
@ -339,13 +292,15 @@ uint8_t TFT_Init(void) {
|
|
|
|
|
TFT_UpdateDistance(0);
|
|
|
|
|
TFT_UpdateDirection(0);
|
|
|
|
|
TFT_UpdateBatteryState(100);
|
|
|
|
|
|
|
|
|
|
return Ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Placeholder for updating the TFT display with GPS data
|
|
|
|
|
void updateDisplay() {
|
|
|
|
|
//Print out our debugging statment.
|
|
|
|
|
Serial.println("updateDisplay()");
|
|
|
|
|
//Serial.println("updateDisplay()");
|
|
|
|
|
|
|
|
|
|
// Disable interrupts while using the dispaly.
|
|
|
|
|
cli();
|
|
|
|
@ -354,12 +309,6 @@ void updateDisplay() {
|
|
|
|
|
// You can add code here to:
|
|
|
|
|
// - Display the current distance between devices using the GPS data
|
|
|
|
|
// - Display any other relevant information, such as GPS coordinates or signal strength
|
|
|
|
|
// Example:
|
|
|
|
|
// tft.setCursor(10, 50); // Set cursor position
|
|
|
|
|
// tft.setTextColor(ST77XX_WHITE); // Set text color
|
|
|
|
|
// tft.setTextSize(2); // Set text size
|
|
|
|
|
// tft.print("Distance: ");
|
|
|
|
|
// tft.print(calculatedDistance);
|
|
|
|
|
|
|
|
|
|
tft.fillScreen(ST77XX_BLACK); // Set the screen background to black
|
|
|
|
|
TFT_UpdateDistance(0);
|
|
|
|
@ -370,50 +319,13 @@ void updateDisplay() {
|
|
|
|
|
sei();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Future function to handle LoRa transmission of GPS data - See adafruit LORA code examples
|
|
|
|
|
void sendGPSData() {
|
|
|
|
|
Serial.println("sendGPSData()");
|
|
|
|
|
// Add logic here to transmit the device's GPS coordinates using LoRa
|
|
|
|
|
// You will need to:
|
|
|
|
|
// - Get the current GPS coordinates
|
|
|
|
|
// - Format the data for transmission
|
|
|
|
|
// - Use the LoRa library to send the data to the paired device
|
|
|
|
|
// Example: LoRa.beginPacket(); LoRa.print(latitude); LoRa.print(longitude); LoRa.endPacket();
|
|
|
|
|
|
|
|
|
|
//Don't waste cpu cycles if no new gps data.
|
|
|
|
|
if(GPS_GetCoordinates() == NoReply){
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(db = RF9X_MIN_DB; db <= RF9X_MAX_DB; db++) {
|
|
|
|
|
Radio_SendMsg(&msg_out);
|
|
|
|
|
if(Radio_CheckForMsg(&msg_in) == Ok) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
Serial.print("TX Power set to:");
|
|
|
|
|
Serial.println(db);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Future function to receive GPS data from the other device via LoRa - See adafruit LORA code examples
|
|
|
|
|
void receiveGPSData() {
|
|
|
|
|
Serial.println("receiveGPSData()");
|
|
|
|
|
// Add logic here to receive the paired device's GPS coordinates using LoRa
|
|
|
|
|
// You will need to:
|
|
|
|
|
// - Listen for incoming LoRa packets
|
|
|
|
|
// - Parse the received data
|
|
|
|
|
// - Update the display and LED ring based on the other device's location
|
|
|
|
|
// Example: if (LoRa.parsePacket()) { double otherLat = LoRa.read(); double otherLon = LoRa.read(); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Future function to handle edge cases (e.g., GPS signal loss, close proximity)
|
|
|
|
|
void handleEdgeCases() {
|
|
|
|
|
Serial.println("handleEdgeCases()");
|
|
|
|
|
//Serial.println("handleEdgeCases()");
|
|
|
|
|
// Add logic here to handle cases where:
|
|
|
|
|
// - GPS signal is lost: Display a message on the screen or flash the LED ring
|
|
|
|
|
// - Devices are too close: Display a warning if the GPS data is unreliable due to proximity
|
|
|
|
|
// - LoRa communication is lost: Display a notification or error on the screen
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|