Good Evening Everyone,
Project Objective
I'm currently being stumped on a problem here for a few days now and I've resorted to finding some help online. The overall objective of this project is to have two modules. One is a Transmitter (TX) and the other a Receiver (RX). Both run on an ESP32 using an SX1276 and M10Q UBLOX GPS Module.
This problem is occurring strictly on the Receiver (RX) end.
I'd like to incorporate an SD Card Reader (preferably a MicroSD Card) to log incoming data (Latitude, Longitude, Altitude, and Velocity) from the Transmitter (TX) using LoRa capabilities and to store it onto the SD Card. However, I'm running into the topic of SPI functions when trying to utilize both the LoRa module and SD Card Module.
I have tested this code and everything works as it's designed. Although when incorporating the SD Card Module and connecting it as follows:
VCC - 5V
GND - GND
MISO - GPIO12
MOSI - GPIO13
SCK - GPIO14
CS - GPIO15
I get "Card Mounting" errors. Although, the LoRa Connection status comes back as "Successful". I'm thinking that these two might not work well together for some odd reason, but maybe one of you might know. If you can help, that'd be great! Thanks!
//(Including External Libraries)
#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <TinyGPSPlus.h>
#include <SD.h>
//(Defines Constants w/ A Name, Value, or Pin Number)
//LoRa
#define SCK 18
#define MISO 19
#define MOSI 23
#define SS 5
#define RST 35
#define DIO0 2
//GPS Frequency Band
#define BAND 915E6
//SSD1306 OLED Display
#define OLED_SDA 21
#define OLED_SCL 22
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
//User Interface Buttons & Actions
#define UP_BUTTON_PIN 25
#define DOWN_BUTTON_PIN 26
#define ENTER_BUTTON_PIN 4
//Piezo Buzzer
#define BUZZER_PIN 27
//SD Card Reader
#define SD_MOSI 13
#define SD_MISO 12
#define SD_SCK 14
#define SD_CS 15
TinyGPSPlus gps;
float latitude = 0.0; // Initialize latitude to 0.0
float longitude = 0.0; // Initialize longitude to 0.0
float latitude2 = 0.0; // Initialize latitude to 0.0
float longitude2 = 0.0; // Initialize longitude to 0.0
float altitude = 0.0; // Initialize altitude to 0.0
float velocity = 0.0; // Initialize speed to 0.0
float hdop = 0.0; // Initialize HDOP to 0.0
float maxAltitude = 0.0; // Initialize maximum altitude to 0.0
float maxVelocity = 0.0; // Initialize maximum speed to 0.0
double calculateDistance(float lat1, float lon1, float lat2, float lon2) {
const double EarthRadius = 6371000.0; // Radius of the Earth in meters
// Convert latitude and longitude from degrees to radians
double lat1Rad = radians(lat1);
double lon1Rad = radians(lon1);
double lat2Rad = radians(lat2);
double lon2Rad = radians(lon2);
// Haversine formula
double dLat = lat2Rad - lat1Rad;
double dLon = lon2Rad - lon1Rad;
double a = sin(dLat / 2) * sin(dLat / 2) + cos(lat1Rad) * cos(lat2Rad) * sin(dLon / 2) * sin(dLon / 2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
double distance = EarthRadius * c;
return distance;
}
//(GPS Declarations)
static const int RXPin = 16;
static const int TXPin = 17;
static const uint32_t GPSBaud = 9600;
//(SSD1306 Display Screen Size)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
//(Main Menu)
const int MENU_OPTION_COUNT = 5;
const char* MENU_OPTIONS[MENU_OPTION_COUNT] = {
"Launch Status",
"Telemetry",
"Launch Results",
"Locator",
"Landing Site"
};
//(Main Menu WiFi Icon)
const unsigned char wifiicon[] PROGMEM ={
0x00, 0xff, 0x00, 0x7e, 0x00, 0x18,0x00, 0x00
};
//(Drawing a Filled Triangle for Selection Highlight)
void drawSelectionTriangle(int x, int y) {
display.fillTriangle(x, y, x, y + 8, x + 4, y + 4, WHITE);
}
//(Variable Declarations for User Interface Control and Timing)
int currentOption = 0;
bool enterButtonPressed = false;
bool displayLaunchStatusPage = false;
unsigned long enterButtonPressStartTime = 0;
//(Variable Declarations for RSSI (Received Signal Strength Indicator) Measurement)
int rssi = 0; // Store the RSSI value
int rssiPercentage = 0; // Store the RSSI percentage
//(Button State Detection and Long-Press Check Functions)
bool isUpButtonPressed() {
return digitalRead(UP_BUTTON_PIN) == LOW;
}
bool isDownButtonPressed() {
return digitalRead(DOWN_BUTTON_PIN) == LOW;
}
bool isEnterButtonLongPressed() {
return enterButtonPressed && (millis() - enterButtonPressStartTime >= 3000);
}
void handleEnterButton() {
if (digitalRead(ENTER_BUTTON_PIN) == LOW) {
if (!enterButtonPressed) {
enterButtonPressStartTime = millis();
enterButtonPressed = true;
}
} else {
enterButtonPressed = false;
}
}
//(Displaying a Startup Boot Screen)
void displayBootScreen() {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
int logoWidth = strlen("ORBITFORWARD") * 6; // Each character is 6 pixels wide with font size 1
int x = (SCREEN_WIDTH - logoWidth) / 2;
display.setCursor(x, (SCREEN_HEIGHT - 8) / 2);
display.println("ORBITFORWARD");
display.display();
// Play a Simple Startup Tune
tone(BUZZER_PIN, 10000, 100); // Play a 400Hz tone for 200 milliseconds
delay(150); // Delay for 100 milliseconds
tone(BUZZER_PIN, 5000, 100); // Play a 500Hz tone for 200 milliseconds
tone(BUZZER_PIN, 10000, 100); // Play a 400Hz tone for 200 milliseconds
delay(150); // Delay for 100 milliseconds
tone(BUZZER_PIN, 5000, 100); // Play a 500Hz tone for 200 milliseconds
tone(BUZZER_PIN, 10000, 100); // Play a 400Hz tone for 200 milliseconds
delay(150); // Delay for 100 milliseconds
tone(BUZZER_PIN, 5000, 100); // Play a 500Hz tone for 200 milliseconds
delay(2000); // Display boot screen for 5 seconds
}
//(Displaying the Main Menu with RSSI Information)
void displayMainMenu() {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("MAIN MENU|");
rssi = LoRa.packetRssi();
rssiPercentage = map(rssi, -120, -40, 0, 99);
if (rssiPercentage < 0) {
display.drawBitmap(98,0,wifiicon,8,8,WHITE);
display.setCursor(110, 0);
display.println("N/A");
} else {
if (rssiPercentage > 99) rssiPercentage = 99;
display.drawBitmap(98,0,wifiicon,8,8,WHITE);
display.setCursor(110, 0);
display.print(LoRa.packetRssi());
}
for (int i = 0; i < MENU_OPTION_COUNT; i++) {
if (i == currentOption) {
drawSelectionTriangle(5, (i * 10) + 9);
display.setTextColor(WHITE);
} else {
display.setTextColor(WHITE);
}
display.setCursor(15, (i * 10) + 10);
display.println(MENU_OPTIONS[i]);
}
display.display();
}
//(Displaying Launch Status Information)
void displayLaunchStatus() {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("LAUNCH STATUS");
display.println();
display.print("LoRa Status: ");
if (LoRa.begin(BAND)) {
display.println("GO");
} else {
display.println("NO-GO");
}
display.print("GPS Status: ");
display.println("GO");
display.display();
}
//(Displaying Telemetry Information)
void displayTelemetry() {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
int displayWidth = SCREEN_WIDTH; // Get the display width
display.setCursor(0, 0);
display.println("TELEMETRY");
display.println();
// Calculate the X position for the labels and readings to be flush on the right side
int labelX = 0; // X position for labels
int readingX = displayWidth - 65; // X position for readings (adjust as needed)
// Display altitude label and reading
display.setCursor(labelX, 16);
display.print("ALTITUDE: ");
display.setCursor(readingX, 16);
display.println(altitude, 2);
// Display speed label and reading
display.setCursor(labelX, 24);
display.print("VELOCITY: ");
display.setCursor(readingX, 24);
display.println(velocity, 2);
display.display();
}
//(Displaying Launch Results Information)
void displayLaunchResults() {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("LAUNCH RESULTS");
display.println();
display.print("MAX ALT: ");
display.println(maxAltitude, 2);
display.print("MAX SPEED: ");
display.println(maxVelocity, 2);
display.display();
}
//(Displaying Locator Information)
void displayLocator() {
// Calculate distance
double dist = calculateDistance(latitude2, longitude2, latitude, longitude);
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("LOCATOR");
display.println();
// Display the calculated distance
display.print("Distance: ");
display.print(dist, 2); // Display distance with 2 decimal places
display.println(" m");
display.display();
}
//(Displaying Landing Site Information)
void displayLandingSite() {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("LANDING SITE");
display.println();
display.print("LAT N: ");
display.println(latitude, 6);
display.print("LON W: ");
display.println(longitude, 6);
display.print("Distance Away: ");
display.println("123.45 m");
display.print("Bearing: ");
display.println("270 deg");
display.display();
}
void readGPSData() {
while (Serial2.available() > 0) {
if (gps.encode(Serial2.read())) {
if (gps.location.isValid()) {
latitude2 = gps.location.lat();
longitude2 = gps.location.lng();
}
}
}
}
void writeToSDCard(const String &data) {
File dataFile = SD.open("data.txt", FILE_WRITE); // Open the data file in write mode
if (dataFile) {
dataFile.println(data); // Write the data to the file
dataFile.close(); // Close the file
Serial.println("Data written to SD card: " + data);
} else {
Serial.println("Error opening data file on SD card.");
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Setup
void setup() {
Serial.begin(115200); //Serial Communication w/ ESP32
Serial2.begin(GPSBaud); //Serial Communication w/ NEO6M GPS
LoRa.setSyncWord(0x12); //LoRa Sync Word Between Transmitter & Receiver
SPI.begin(SCK, MISO, MOSI, SS);
LoRa.setPins(SS, RST, DIO0);
pinMode(UP_BUTTON_PIN, INPUT_PULLUP);
pinMode(DOWN_BUTTON_PIN, INPUT_PULLUP);
pinMode(ENTER_BUTTON_PIN, INPUT_PULLUP);
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C, false, false)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.clearDisplay();
displayBootScreen();
if (!LoRa.begin(BAND)) {
Serial.println("LoRa Connection Failed");
while (1);
}
Serial.println("LoRa Connection Succeeded");
// Initialize the SD card
if (!SD.begin(SD_CS)) {
Serial.println("SD card initialization failed!");
while (1);
}
Serial.println("SD card initialized successfully.");
pinMode(BUZZER_PIN, OUTPUT);
noTone(BUZZER_PIN);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Loop
void loop() {
handleEnterButton(); //Responsible for Detecting/Managing Enter Button Presses
readGPSData();
if (isUpButtonPressed()) { //If the "UP" Button is Pressed...
delay(200); //Delay of 0.2 Seconds
currentOption--; //Moves to Previous Selection
if (currentOption < 0) {
currentOption = MENU_OPTION_COUNT - 1;
}
} else if (isDownButtonPressed()) { //If the "DOWN" Button is Pressed...
delay(200); //Delay of 0.2 Seconds
currentOption++; //Moves to Previous Selection
if (currentOption >= MENU_OPTION_COUNT) {
currentOption = 0;
}
}
if (isEnterButtonLongPressed()) { //If the "ENTER" Button is Pressed & Held...
currentOption = 0;
displayLaunchStatusPage = false; // Return to the main menu
} else if (digitalRead(ENTER_BUTTON_PIN) == LOW) {
delay(200); //Delay of 0.2 Seconds
displayLaunchStatusPage = true; // Show selected page
}
if (displayLaunchStatusPage) { //If True, It Should Display a Specific Page
switch (currentOption) { //Depends On What Value "currentOption" Is
case 0: //If "0"
displayLaunchStatus(); //Then Display Launch Status Screen
break;
case 1: //If "1"
displayTelemetry(); //Then Display Telemetry Screen
break;
case 2: //If "2"
displayLaunchResults(); //Then Display Launch Results Screen
break;
case 3: //If "3"
displayLocator(); //Then Display Locator Screen
break;
case 4: //If "4"
displayLandingSite(); //Then Display Landing Site Screen
break;
}
} else { //If Untrue, Then It Should Display the Main Menu
displayMainMenu(); //Display Main Menu
}
// Process LoRa data
int packetSize = LoRa.parsePacket();
if (packetSize) {
while (LoRa.available()) {
String data = LoRa.readString();
Serial.print(data);
// Write the received data to the SD card
writeToSDCard(data);
// Search for and extract values based on the data labels
int latIndex = data.indexOf("LAT:");
if (latIndex != -1) {
latitude = data.substring(latIndex + 4).toFloat();
}
int lonIndex = data.indexOf("LON:");
if (lonIndex != -1) {
longitude = data.substring(lonIndex + 4).toFloat();
}
int altIndex = data.indexOf("ALT:");
if (altIndex != -1) {
altitude = data.substring(altIndex + 4).toFloat();
if (altitude > maxAltitude) {
maxAltitude = altitude;
}
}
int velocityIndex = data.indexOf("VELO:");
if (velocityIndex != -1) {
velocity = data.substring(velocityIndex + 5).toFloat();
if (velocity > maxVelocity) {
maxVelocity = velocity;
}
}
}
}
}