nRF24 Stops Working When SD Card is Plugged In (NodeMCU + RF24 + SD + MPU6050 + BMP280)

I'm working on a NodeMCU project that uses an nRF24L01, SD card, MPU6050, and BMP280. Individually, both the SD card and nRF24 work fine, but when I plug in the SD card, the nRF24 stops working (doesn't send or receive data).

Hardware Setup: NodeMCU ESP8266 nRF24L01 (CE: D4 (GPIO2), CSN: D8 (DPIO15)) SD card module (CS: D0(GPIO16)) Works Perfectly - MPU6050 & BMP280 (I2C - SDA: D2, SCL: D1) Works Perfectly - Servo on D0 Code Snippet:

#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>
#include <SD.h>
#include <Servo.h> // Include Servo library
#include <RF24.h>

RF24 radio(2, 15); // CE, CSN pins on NodeMCU

#define BMP_SCK  (13)
#define BMP_MISO (12)
#define BMP_MOSI (11)
#define BMP_CS   (10)
#define APOGEE_THRESHOLD 1.5 // Adjust this threshold as needed

Servo parachuteServo; // Define servo object

// Constants for smoothing
const int numReadings = 10;
float accelXReadings[numReadings];
float accelYReadings[numReadings];
float accelZReadings[numReadings];
float pressureReadings[numReadings];
int readIndex = 0;

float sumAccelX = 0.0;
float sumAccelY = 0.0;
float sumAccelZ = 0.0;
float sumPressure = 0.0;

float averageAccelX = 0.0;
float averageAccelY = 0.0;
float averageAccelZ = 0.0;
float averagePressure = 0.0;

// Function prototypes
void initializeMPU();
void initializeBMP();
void initializeSDCard();
void saveDataToFile(const char *filename, String data);

Adafruit_BMP280 bmp;
Adafruit_MPU6050 mpu;

float initialAltitude = 0.0; // Initial altitude
float initialZAccel = 0.0;   // Initial Z acceleration
unsigned long prevTime = 0;  // Previous time for time difference calculation

bool shakeDetected = true; // Flag to indicate if shake is detected
bool loggingStarted = false; // Flag to indicate if data logging has started
bool chuteDeployed = false;

// Addresses for communication
const byte slaveAddress[5] = {'R','x','A','A','A'};
const byte masterAddress[5] = {'T','X','a','a','a'};

// Initialize the nRF24 radio
char dataToSend[32];  // Increased buffer size for sensor data
char receivedCommand[10] = {0}; // Buffer for received command
bool newData = false;

void setup() {
  Serial.begin(115200);
  while (!Serial) {
    delay(10);
  }

  initializeMPU();
  initializeBMP();
  initializeSDCard();

  radio.begin();
  radio.setDataRate(RF24_250KBPS);
  radio.openWritingPipe(slaveAddress);
  radio.openReadingPipe(1, masterAddress);
  radio.setRetries(3, 5);
  radio.startListening();

  Serial.println("Transmitter ready!");

  // Wait for shake detection
  while (!shakeDetected) {
    sensors_event_t a, g, temp;
    mpu.getEvent(&a, &g, &temp);

    // Calculate total acceleration magnitude
    float totalAccel = sqrt(pow(a.acceleration.x, 2) + pow(a.acceleration.y, 2) + pow(a.acceleration.z, 2));

    // Check if total acceleration exceeds threshold for shake detection
    if (totalAccel > 12.0) { // Adjust threshold as needed
      shakeDetected = true;
    }

    delay(100); // Adjust delay as needed
  }

  Serial.println("Shake detected!");

  parachuteServo.attach(0); // Attach servo to pin number
  parachuteServo.write(0);
  delay(300);

  // Initialize smoothing arrays
  for (int i = 0; i < numReadings; i++) {
    accelXReadings[i] = 0.0;
    accelYReadings[i] = 0.0;
    accelZReadings[i] = 0.0;
    pressureReadings[i] = 0.0;
  }
}

void loop() {
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);

  float accelX = a.acceleration.x;
  float accelY = a.acceleration.y;
  float accelZ = a.acceleration.z;
  float gyroX = g.gyro.x;
  float gyroY = g.gyro.y;
  float gyroZ = g.gyro.z;

  // Update smoothing arrays
  sumAccelX -= accelXReadings[readIndex];
  sumAccelY -= accelYReadings[readIndex];
  sumAccelZ -= accelZReadings[readIndex];
  sumPressure -= pressureReadings[readIndex];

  accelXReadings[readIndex] = accelX;
  accelYReadings[readIndex] = accelY;
  accelZReadings[readIndex] = accelZ;
  pressureReadings[readIndex] = bmp.readPressure(); // Read pressure here

  sumAccelX += accelXReadings[readIndex];
  sumAccelY += accelYReadings[readIndex];
  sumAccelZ += accelZReadings[readIndex];
  sumPressure += pressureReadings[readIndex];

  averageAccelX = sumAccelX / numReadings;
  averageAccelY = sumAccelY / numReadings;
  averageAccelZ = sumAccelZ / numReadings;
  averagePressure = sumPressure / numReadings;

  readIndex = (readIndex + 1) % numReadings;

  float temperature = bmp.readTemperature();

  // Calculate altitude using provided BMP280 library code
  float currentAltitude = bmp.readAltitude(1013.25); /* Adjusted to local forecast! */
  float currentAltitudeFt = currentAltitude * 3.28084; // Convert altitude to feet

  // Calculate relative altitude
  int relativeAltitude = (int)(currentAltitudeFt - initialAltitude); // Convert to integer

  // Calculate vertical speed (Z acceleration)
  unsigned long currentTime = millis();
  float deltaTime = (currentTime - prevTime) / 1000.0; // Convert to seconds
  float currentZAccel = (averageAccelZ - initialZAccel) * 9.81; // Convert from g to m/s^2
  float verticalSpeed = currentZAccel * deltaTime * 3.6; // Convert from m/s to km/h
  float maxAltitude = 0.0;    // Variable to store the maximum altitude
  bool apogeeDetected = false;

  // Update previous time for next iteration
  prevTime = currentTime;

  // Prepare data packet
  char dataToSend[128]; // Buffer large enough to hold full data
  snprintf(dataToSend, sizeof(dataToSend), 
           "AccelX:%.2f, AccelY:%.2f, AccelZ:%.2f, GyroX:%.2f, GyroY:%.2f, GyroZ:%.2f, "
           "Altitude:%d, VSpeed:%.2f", 
           averageAccelX, averageAccelY, averageAccelZ, 
           gyroX, gyroY, gyroZ, 
           relativeAltitude, 
           verticalSpeed);
  
  // Send data in chunks
  sendDataInChunks(dataToSend);

  getData();
  if (newData) {
      executeCommand();
      newData = false;
  }
  checkSerialCommand();

  Serial.println("Data sent: " + String(dataToSend));

  // Print structured data to Serial monitor
  Serial.print("AccelX:");
  Serial.print(accelX);
  Serial.print(", AccelY:");
  Serial.print(accelY);
  Serial.print(", AccelZ:");
  Serial.print(accelZ);
  Serial.print(", GyroX:");
  Serial.print(gyroX);
  Serial.print(", GyroY:");
  Serial.print(gyroY);
  Serial.print(", GyroZ:");
  Serial.print(gyroZ);
  Serial.print(", Altitude:");
  Serial.print(relativeAltitude);
  Serial.print(", VSpeed:");
  Serial.print(verticalSpeed);
  Serial.println("");


  if (accelZ < 0) {
    Serial.println("Negative AccelZ detected! Rotating servo...");
    parachuteServo.write(180); // Rotate servo to 180 degrees
    delay(600); // Allow time for servo to rotate
    parachuteServo.write(0); // Return servo to initial position
    delay(600); // Allow time for servo to rotate back
  }

  delay(100); // Adjust delay as needed

  // Check for serial input and process it
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n'); // Read until newline

    // Check if command is "deploy" or "eject"
    if (command.equals("a") || command.equals("eject")) {
      Serial.println("Chute Deployed!");
      parachuteServo.write(180); // Rotate servo to 180 degrees
      delay(600); // Allow time for servo to rotate
    }

    // Check if command is "deploy" or "eject"
    if (command.equals("initial") || command.equals("b")) {
      Serial.println("Chute Ready!");
      parachuteServo.write(0); // Set to a neutral or initial position
      delay(600); // Allow time for servo to rotate
    }

    // Check if command is "test" or "t"
if (command.equals("test") || command.equals("t")) {
  Serial.println("Testing components...");

  int pos;
  for (pos = 0; pos <= 180; pos += 5) {
    parachuteServo.write(pos);
    delay(15);
  }
  for (pos = 190; pos >= 0; pos -= 1) {
    parachuteServo.write(pos);
    delay(15);
  }
  delay(2000);

  // Test MPU
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);
  Serial.println("MPU6050 Test:");
  Serial.print("AccelX: "); Serial.print(a.acceleration.x); Serial.print(" ");
  Serial.print("AccelY: "); Serial.print(a.acceleration.y); Serial.print(" ");
  Serial.print("AccelZ: "); Serial.print(a.acceleration.z); Serial.println(" ");
  Serial.print("GyroX: "); Serial.print(g.gyro.x); Serial.print(" ");
  Serial.print("GyroY: "); Serial.print(g.gyro.y); Serial.print(" ");
  Serial.print("GyroZ: "); Serial.print(g.gyro.z); Serial.println(" ");
  Serial.println("MPU6050 Test Complete!");
  delay(2000);

  // Test BMP
  float temperature = bmp.readTemperature();
  float pressure = bmp.readPressure();
  float altitude = bmp.readAltitude(1013.25);
  Serial.println("BMP280 Test:");
  Serial.print("Temperature: "); Serial.print(temperature); Serial.print(" C ");
  Serial.print("Pressure: "); Serial.print(pressure); Serial.print(" Pa ");
  Serial.print("Altitude: "); Serial.print(altitude); Serial.println(" m ");
  Serial.println("BMP280 Test Complete!");
  delay(2000);

  // Test SD Card
  if (SD.begin(16)) {
    Serial.println("SD Card Test: SD card initialized successfully.");
    File testFile = SD.open("testfile.txt", FILE_WRITE);
    if (testFile) {
      testFile.println("SD Card Test Successful!");
      testFile.close();
      Serial.println("SD Card Test: Test file written successfully.");
    } else {
      Serial.println("SD Card Test: Failed to open file for writing.");
    }
  } else {
    Serial.println("SD Card Test: Failed to initialize SD card.");
  }
  delay(2000);
}


    // Check if "start log" command received and logging not started yet
    if (command.equals("start log") && !loggingStarted || command.equals("l") && !loggingStarted) {
      loggingStarted = true;
      Serial.println("Data logging started!");
    }

    // Check if "stop log" command received and logging currently active
    if (command.equals("stop log") && loggingStarted || command.equals("s") && loggingStarted) {
      loggingStarted = false;
      Serial.println("Data logging stopped!");
    }
  }

  // Save sensor data to SD card if logging started
  if (loggingStarted) {
    saveDataToFile("datalogg.csv", String(averageAccelX) + "," + String(averageAccelY) + "," + String(averageAccelZ) + "," +
                   String(gyroX) + "," + String(gyroY) + "," + String(gyroZ) + "," +
                   String(temperature) + "," + String(averagePressure) + "," + String(relativeAltitude) + "," + String(verticalSpeed));
  }

  // Check for apogee detection if logging started
  if (loggingStarted && !apogeeDetected) {
    if (relativeAltitude > maxAltitude) {
      maxAltitude = relativeAltitude; // Update maximum altitude
    } else if ((maxAltitude - relativeAltitude) > APOGEE_THRESHOLD) {
      // Apogee detected
      apogeeDetected = true;

      // Save apogee data to SD card
      saveApogeeData(maxAltitude, currentTime);
    }
  }

  delay(100); // Adjust delay as needed
}

void initializeMPU() {
  if (!mpu.begin()) {
    Serial.println("Failed to initialize MPU6050!");
    while (1);
  }
  Serial.println("MPU6050 initialized.");
}

void initializeBMP() {
  if (!bmp.begin(BMP280_ADDRESS_ALT, BMP280_CHIPID)) {
    Serial.println("Failed to initialize BMP280!");
    while (1);
  }
  Serial.println("BMP280 initialized.");
  initialAltitude = bmp.readAltitude(1013.25); // Set initial altitude
  initialZAccel = 0.0;
}

void initializeSDCard() {
  // Ensure BMP280 is not using the SPI bus
  digitalWrite(BMP_CS, HIGH);

  if (!SD.begin(16)) {
    Serial.println("Failed to initialize SD card!");  
    // while (1);
  }
  Serial.println("SD card initialized.");
}

void sendDataInChunks(const char *data) {
    int dataLength = strlen(data);
    int chunkSize = 32;
    int chunks = (dataLength / chunkSize) + ((dataLength % chunkSize) > 0 ? 1 : 0);

    for (int i = 0; i < chunks; i++) {
        radio.stopListening();
        char chunk[32] = {0};  // Initialize buffer with null characters
        strncpy(chunk, data + (i * chunkSize), chunkSize); // Copy 32 bytes or remaining bytes

        radio.write(chunk, sizeof(chunk));  // Send chunk
        radio.startListening();  // Start listening again
        // delay(10); // Short delay to ensure proper transmission
    }
}

void getData() {
    if (radio.available()) {
        radio.read(&receivedCommand, sizeof(receivedCommand));
        newData = true;
    }
}

void executeCommand() {
    String command = String(receivedCommand);
    command.trim(); // Remove any extra spaces or newlines

    // Check if command is "deploy" or "eject"
    if (command.equals("a") || command.equals("eject")) {
        Serial.println("Chute Deployed!");
        parachuteServo.write(180); // Rotate servo to 180 degrees
        delay(600); // Allow time for servo to rotate
    }
    // Check if command is "initial" or "b"
    else if (command.equals("initial") || command.equals("b")) {
        Serial.println("Chute Ready!");
        parachuteServo.write(0); // Set to a neutral or initial position
        delay(600); // Allow time for servo to rotate
    }
    // Check if command is "test" or "t"
    else if (command.equals("test") || command.equals("t")) {
        Serial.println("Testing components...");

        // Test parachute servo movement
        int pos;
        for (pos = 0; pos <= 180; pos += 5) {
            parachuteServo.write(pos);
            delay(15);
        }
        for (pos = 180; pos >= 0; pos -= 5) { // Adjusted range to start from 180 and decrement by 5
            parachuteServo.write(pos);
            delay(15);
        }
        delay(2000);

        // Test MPU
        sensors_event_t a, g, temp;
        mpu.getEvent(&a, &g, &temp);
        Serial.println("MPU6050 Test:");
        Serial.print("AccelX: "); Serial.print(a.acceleration.x); Serial.print(" ");
        Serial.print("AccelY: "); Serial.print(a.acceleration.y); Serial.print(" ");
        Serial.print("AccelZ: "); Serial.print(a.acceleration.z); Serial.println(" ");
        Serial.print("GyroX: "); Serial.print(g.gyro.x); Serial.print(" ");
        Serial.print("GyroY: "); Serial.print(g.gyro.y); Serial.print(" ");
        Serial.print("GyroZ: "); Serial.print(g.gyro.z); Serial.println(" ");
        Serial.println("MPU6050 Test Complete!");
        delay(2000);

        // Test BMP
        float temperature = bmp.readTemperature();
        float pressure = bmp.readPressure();
        float altitude = bmp.readAltitude(1013.25);
        Serial.println("BMP280 Test:");
        Serial.print("Temperature: "); Serial.print(temperature); Serial.print(" C ");
        Serial.print("Pressure: "); Serial.print(pressure); Serial.print(" Pa ");
        Serial.print("Altitude: "); Serial.print(altitude); Serial.println(" m ");
        Serial.println("BMP280 Test Complete!");
        delay(2000);

        // Test SD Card
        if (SD.begin(16)) {
            Serial.println("SD Card Test: SD card initialized successfully.");
            File testFile = SD.open("testfile.txt", FILE_WRITE);
            if (testFile) {
                testFile.println("SD Card Test Successful!");
                testFile.close();
                Serial.println("SD Card Test: Test file written successfully.");
            } else {
                Serial.println("SD Card Test: Failed to open file for writing.");
            }
        } else {
            Serial.println("SD Card Test: Failed to initialize SD card.");
        }
        delay(2000);
    }
    // Check if "start log" command received and logging not started yet
    else if ((command.equals("start log") || command.equals("l")) && !loggingStarted) {
        loggingStarted = true;
        Serial.println("Data logging started!");
    }
    // Check if "stop log" command received and logging currently active
    else if ((command.equals("stop log") || command.equals("s")) && loggingStarted) {
        loggingStarted = false;
        Serial.println("Data logging stopped!");
    }
    else {
        Serial.println("Unknown command!");
    }

    memset(receivedCommand, 0, sizeof(receivedCommand)); // Clear buffer
}

void checkSerialCommand() {
    if (Serial.available() > 0) {
        String command = Serial.readStringUntil('\n');
        command.trim();
        executeCommandFromSerial(command);
    }
}

void executeCommandFromSerial(String command) {
    // Check if command is "deploy" or "eject"
    if (command.equals("a") || command.equals("eject")) {
        Serial.println("Chute Deployed!");
        parachuteServo.write(180); // Rotate servo to 180 degrees
        delay(600); // Allow time for servo to rotate
    }
    // Check if command is "initial" or "b"
    else if (command.equals("initial") || command.equals("b")) {
        Serial.println("Chute Ready!");
        parachuteServo.write(0); // Set to a neutral or initial position
        delay(600); // Allow time for servo to rotate
    }
    // Check if command is "test" or "t"
    else if (command.equals("test") || command.equals("t")) {
        Serial.println("Testing components...");

        // Test parachute servo movement
        int pos;
        for (pos = 0; pos <= 180; pos += 5) {
            parachuteServo.write(pos);
            delay(15);
        }
        for (pos = 180; pos >= 0; pos -= 5) { // Adjusted range to start from 180 and decrement by 5
            parachuteServo.write(pos);
            delay(15);
        }
        delay(2000);

        // Test MPU
        sensors_event_t a, g, temp;
        mpu.getEvent(&a, &g, &temp);
        Serial.println("MPU6050 Test:");
        Serial.print("AccelX: "); Serial.print(a.acceleration.x); Serial.print(" ");
        Serial.print("AccelY: "); Serial.print(a.acceleration.y); Serial.print(" ");
        Serial.print("AccelZ: "); Serial.print(a.acceleration.z); Serial.println(" ");
        Serial.print("GyroX: "); Serial.print(g.gyro.x); Serial.print(" ");
        Serial.print("GyroY: "); Serial.print(g.gyro.y); Serial.print(" ");
        Serial.print("GyroZ: "); Serial.print(g.gyro.z); Serial.println(" ");
        Serial.println("MPU6050 Test Complete!");
        delay(2000);

        // Test BMP
        float temperature = bmp.readTemperature();
        float pressure = bmp.readPressure();
        float altitude = bmp.readAltitude(1013.25);
        Serial.println("BMP280 Test:");
        Serial.print("Temperature: "); Serial.print(temperature); Serial.print(" C ");
        Serial.print("Pressure: "); Serial.print(pressure); Serial.print(" Pa ");
        Serial.print("Altitude: "); Serial.print(altitude); Serial.println(" m ");
        Serial.println("BMP280 Test Complete!");
        delay(2000);

        // Test SD Card
        if (SD.begin(16)) {
            Serial.println("SD Card Test: SD card initialized successfully.");
            File testFile = SD.open("testfile.txt", FILE_WRITE);
            if (testFile) {
                testFile.println("SD Card Test Successful!");
                testFile.close();
                Serial.println("SD Card Test: Test file written successfully.");
            } else {
                Serial.println("SD Card Test: Failed to open file for writing.");
            }
        } else {
            Serial.println("SD Card Test: Failed to initialize SD card.");
        }
        delay(2000);
    }
    // Check if "start log" command received and logging not started yet
    else if ((command.equals("start log") || command.equals("l")) && !loggingStarted) {
        loggingStarted = true;
        Serial.println("Data logging started!");
    }
    // Check if "stop log" command received and logging currently active
    else if ((command.equals("stop log") || command.equals("s")) && loggingStarted) {
        loggingStarted = false;
        Serial.println("Data logging stopped!");
    }
    else {
        Serial.println("Unknown command!");
    }
}

void saveDataToFile(const char *filename, String data) {
  Serial.print("Attempting to open file: ");
  Serial.println(filename);

  File file = SD.open(filename, FILE_WRITE);
  if (file) {
    file.println(data);
    file.close();
    Serial.println("Data saved to file.");
  } else {
    Serial.println("Failed to open file for writing.");
  }
}

void saveApogeeData(float maxAltitude, unsigned long apogeeTime) {
  Serial.println("Attempting to save apogee data...");
  File file = SD.open("apogeee.csv", FILE_WRITE);
  if (file) {
    file.print("MaxAltitude: ");
    file.print(maxAltitude);
    file.print(" ft, Time: ");
    file.println(apogeeTime);
    file.close();
    Serial.println("Apogee data saved.");
  } else {
    Serial.println("Failed to open file for writing apogee data.");
  }
}

String getDataString() {
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);

  float accelX = a.acceleration.x;
  float accelY = a.acceleration.y;
  float accelZ = a.acceleration.z;
  float gyroX = g.gyro.x;
  float gyroY = g.gyro.y;
  float gyroZ = g.gyro.z;
  float pressure = bmp.readPressure();
  float temperature = bmp.readTemperature();
  float currentAltitude = bmp.readAltitude(1013.25);
  float currentAltitudeFt = currentAltitude * 3.28084;
  int relativeAltitude = (int)(currentAltitudeFt - initialAltitude); // Convert to integer
  unsigned long currentTime = millis();
  float deltaTime = (currentTime - prevTime) / 1000.0;
  float currentZAccel = (accelZ - initialZAccel) * 9.81;
  float verticalSpeed = currentZAccel * deltaTime * 3.6;

  // Prepare data packet
  String dataPacket = String(accelX) + "," + String(accelY) + "," + String(accelZ) + "," +
                     String(gyroX) + "," + String(gyroY) + "," + String(gyroZ) + "," +
                     String(relativeAltitude) + "," + String(verticalSpeed);

  return dataPacket;
}

Issue: SD card and nRF24 work fine separately. When SD is plugged in, nRF24 stops working (doesn't send or receive data). No errors, just no radio communication.

Using different CS pins → SD (D10), nRF (D15) Changing SPI speeds → SD.begin(10, SPI_HALF_SPEED); Checking power supply → SD and nRF24 get 3.3V from NodeMCU Adding decoupling capacitors (330µF) to nRF24

can you add a photo of the SD card module?

if the SD card is sharing a SPI bus with other devices there can be problems
Sharing the SPI bus among SD card and other SPI devices
using ESP32 I usually have the SD card on the HSPI interface and other SPI devices on the VSPI

Post an annotated schematic showing exactly how you have wired it. Include all connections, power, ground, power sources and any extra components.


is this image fine?

just buy an SD card module without the 5 V conversion

1 Like

Yes, your SD card module has logic-level shifting chip on it so it can be connected to a 5V Arduino (all SD cards run at 3.3V).

Most, maybe all, of these have a design fault which means they won't share the SPI bus with other SPI devices.

A module without the logic-level shifting chip should work better.

1 Like

Your SD card module looks strange. It looks like the voltage regulator chip is missing. Also the board looks a little heat damaged around the pads where the regulator would be.

As you are using an RF24-module you seem to send data wireless.

Why is it no option to use the WiFi of the ESP8266?

The ESP8266 and ESP32-microcontroller offer a direct wireless connection
which is called ESP-NOW.

Have you ever heard of it?

1 Like

Include all connections, power, ground, power sources and any extra components.

but the range is not that good right? I am making this project to use as a flight computer (requires quite a long range), thats why

for some reason my SD wasnt working with it, so I removed it long ago...I tried another SD module with the regulator, that didnt work at all. The one without the regulator atleast works perfectly but not when my nrf is plugged in

2,4 GHz in general is a bad choice for long distancies. Except you use somehow devices with a higher power and/or a real antenna.

And this leads to this question: from where do you supply the RF24-module with power?
Do you supply it from the 5V / 3.3V pin of the microcontroller?

nRF24-modules are well known for power-issues.

For long distancies modules sending at 433MHz are better suited.
What will be the maximum distance that can happen?

Unfortunately, I have the esp8266 which only has one SPI bus and I am unable to switch to esp32 now because its a pcb (dimensions and wiring is different)

I checked the voltage going in for the sd and nrf when both were plugged in and confirmed that both were getting enough power, most flight computers by hobbyists use NRFs, thats why I used it as well (it works perfectly when the SD is not plugged in, tried it by keeping the transmitter and receiver 200m apart and it worked without the SD)

sure, I'll try this one and see as well

A real check if both get enough power permanently would be to use a digital storage oscilloscope and record the voltage over time. This would make visible if there are very short voltages drops or not.

You can add a big electrolytic capacitor to improve voltage-supply. Big electrolytic capacitor in this case means 2200 µF or more.

This capacitor has to be as close as possible to the Vin and GND-pin as possible.

You haven't described what exact behaviour your device is showing in case "it does not work".

So far it could be a crash of the ESP8266, nRF24 not working, SD-card not working.
Post a picture of exact your real nodeMCU. Not any internet-picture. A real picture of exact that nodeMCU that is laying on your table.

Usually nodeMCU s have a blue onboard LED.
Add a short piece of code that makes this led blink to see if the microcontroller is running at all. If your device "does not work"

could you use software SPI for one of the modules?

The problem is almost certainly as described by @PaulRB. The MISO line is what the SPI devices use to send data back to the MCU. But if there are two SPI devices, they must share that line. This requires that only the device which has its CS line asserted can drive MISO. The other one has to disconnect from MISO (go tristate).

The problem with this SD module is that it runs MISO from the card through the level converter chip, and that chip has all of its /OE lines turned on. In the first place, translating a 3.3V signal from the card into a 3.3V signal obviously accomplishes nothing. But more important, the chip is always driving MISO, which means the other SPI device's attempted transmissions are messed up. That's why both devices work when the other isn't connected, but they don't work together.

The Adafruit microSD module simply connects MISO directly from the card, without trying to translate it. And since the SD card itself knows how to behave, that works fine. It even works fine with a 5V MCU because 3.3V is read as HIGH by the MCU.

There is a fix you may want to try, which is to convert what you have into the Adafruit model. You have to solder a wire from a pin on the card holder directly to the module's MISO header, after cutting the trace going from that header to a nearby via. Then it should work.