NRF24L01+PA+LNA not Working

Hi all!

I am trying to make an RC car using NRF24l01. I designed and had made a PCB for the transmitter (control), and wired together the receiver on a breadboard. On the transmitter side, it is working and sending packets ok. But the receiver side says it isn't receiving anything.

Transmitter schematic:
Schematic_RC Control_2021-11-02.pdf (53.8 KB)

Receiver schematic:
Schematic_NRF24L01 Reciever_2021-11-02.pdf (29.2 KB)

Receiver photo:

Transmitter Code:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>

// Define the digital inputs
#define jB1 2  // Joystick button 1
#define jB2 3  // Joystick button 2
#define jB1x A0   // Joystick x1
#define jB1y A1   // Joystick y1
#define jB2x A2   // Joystick x2
#define jB2y A3   // Joystick y2
#define sw1 6
#define sw2 7
#define p1 4
#define p2 5

const int MPU = 0x68; // MPU6050 I2C address
float AccX, AccY, AccZ;
float GyroX, GyroY, GyroZ;
float accAngleX, accAngleY, gyroAngleX, gyroAngleY;
float angleX, angleY;
float AccErrorX, AccErrorY, GyroErrorX, GyroErrorY;
float elapsedTime, currentTime, previousTime;
int c = 0;

boolean flag = false;

RF24 radio(10, 8);   // nRF24L01 (CE, CSN)
const byte address[6] = "00001"; // Address

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte j1PotX;
  byte j1PotY;
  byte j1Button;
  byte j2PotX;
  byte j2PotY;
  byte j2Button;
  byte pot1;
  byte pot2;
  byte tSwitch2;
};

Data_Package data; //Create a variable with the above structure

void setup() {
  Serial.begin(9600);
  
  // Initialize interface to the MPU6050
  initialize_MPU6050();

  // Call this function if you need to get the IMU error values for your module
  //calculate_IMU_error();
  
  // Define the radio communication
  if (radio.begin()){
    Serial.println("RF started successfully!");
  }
  else {
    Serial.println("RF start failed!");
  }
  //radio.begin();
  radio.openWritingPipe(address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);
  
  // Activate the Arduino internal pull-up resistors
  pinMode(jB1, INPUT_PULLUP);
  pinMode(jB2, INPUT_PULLUP);
  pinMode(sw1, INPUT_PULLUP);
  pinMode(sw2, INPUT_PULLUP);
  pinMode(jB1x, INPUT);
  pinMode(jB1y, INPUT);
  pinMode(jB2x, INPUT);
  pinMode(jB2y, INPUT);
  pinMode(p1, INPUT);
  pinMode(p2, INPUT);
  
  // Set initial default values
  data.j1PotX = 127; // Values from 0 to 255. When Joystick is in resting position, the value is in the middle, or 127. We actually map the pot value from 0 to 1023 to 0 to 255 because that's one BYTE value
  data.j1PotY = 127;
  data.j2PotX = 127;
  data.j2PotY = 127;
  data.j1Button = 1;
  data.j2Button = 1;
  data.pot1 = 0;
  data.pot2 = 0;
  data.tSwitch2 = 1;
  
  Serial.println("Setup finished");
}

void loop() {
  // Read all analog inputs and map them to one Byte value
  data.j1PotX = map(analogRead(jB1x), 0, 1023, 0, 255); // Convert the analog read value from 0 to 1023 into a BYTE value from 0 to 255
  data.j1PotY = map(analogRead(jB1y), 0, 1023, 0, 255);
  data.j2PotX = map(analogRead(jB2x), 0, 1023, 0, 255);
  data.j2PotY = map(analogRead(jB2y), 0, 1023, 0, 255);
  data.pot1 = map(analogRead(p1), 0, 1023, 0, 255);
  data.pot2 = map(analogRead(p2), 0, 1023, 0, 255);
  // Read all digital inputs
  data.j1Button = digitalRead(jB1);
  data.j2Button = digitalRead(jB2);
  data.tSwitch2 = digitalRead(sw2);
  
  // If toggle switch 1 is switched on
  if (digitalRead(sw1) == 0) {
    flag = !flag;
    while (digitalRead(sw1) == 0);
    for (int p=0;p<50;p++){
      Serial.println("switched");
    }
    delay(100);
  }
  Serial.print("Flag:");
  Serial.println(flag);
  if (flag == true){
    Serial.println("MPU6050");
    read_IMU();    // Use MPU6050 instead of Joystick 1 for controling left, right, forward and backward movements
  }

  Serial.print("J1Pot X: ");
  Serial.println(data.j1PotX);
  Serial.print("J1Pot Y: ");
  Serial.println(data.j1PotY);
  
  // Send the whole data from the structure to the receiver
  if (radio.write(&data, sizeof(Data_Package))){
    Serial.println("radio.write successful!");
  }
  delay(200);
}

void initialize_MPU6050() {
  Wire.begin();                      // Initialize comunication
  Wire.beginTransmission(MPU);       // Start communication with MPU6050 // MPU=0x68
  Wire.write(0x6B);                  // Talk to the register 6B
  Wire.write(0x00);                  // Make reset - place a 0 into the 6B register
  Wire.endTransmission(true);        //end the transmission
  // Configure Accelerometer
  Wire.beginTransmission(MPU);
  Wire.write(0x1C);                  //Talk to the ACCEL_CONFIG register
  Wire.write(0x10);                  //Set the register bits as 00010000 (+/- 8g full scale range)
  Wire.endTransmission(true);
  // Configure Gyro
  Wire.beginTransmission(MPU);
  Wire.write(0x1B);                   // Talk to the GYRO_CONFIG register (1B hex)
  Wire.write(0x10);                   // Set the register bits as 00010000 (1000dps full scale)
  Wire.endTransmission(true);
}

void calculate_IMU_error() {
  // We can call this funtion in the setup section to calculate the accelerometer and gury data error. From here we will get the error values used in the above equations printed on the Serial Monitor.
  // Note that we should place the IMU flat in order to get the proper values, so that we then can the correct values
  // Read accelerometer values 200 times
  while (c < 200) {
    Wire.beginTransmission(MPU);
    Wire.write(0x3B);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU, 6, true);
    AccX = (Wire.read() << 8 | Wire.read()) / 4096.0 ;
    AccY = (Wire.read() << 8 | Wire.read()) / 4096.0 ;
    AccZ = (Wire.read() << 8 | Wire.read()) / 4096.0 ;
    // Sum all readings
    AccErrorX = AccErrorX + ((atan((AccY) / sqrt(pow((AccX), 2) + pow((AccZ), 2))) * 180 / PI));
    AccErrorY = AccErrorY + ((atan(-1 * (AccX) / sqrt(pow((AccY), 2) + pow((AccZ), 2))) * 180 / PI));
    c++;
  }
  //Divide the sum by 200 to get the error value
  AccErrorX = AccErrorX / 200;
  AccErrorY = AccErrorY / 200;
  c = 0;
  // Read gyro values 200 times
  while (c < 200) {
    Wire.beginTransmission(MPU);
    Wire.write(0x43);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU, 4, true);
    GyroX = Wire.read() << 8 | Wire.read();
    GyroY = Wire.read() << 8 | Wire.read();
    // Sum all readings
    GyroErrorX = GyroErrorX + (GyroX / 32.8);
    GyroErrorY = GyroErrorY + (GyroY / 32.8);
    c++;
  }
  //Divide the sum by 200 to get the error value
  GyroErrorX = GyroErrorX / 200;
  GyroErrorY = GyroErrorY / 200;
  // Print the error values on the Serial Monitor
  Serial.print("AccErrorX: ");
  Serial.println(AccErrorX);
  Serial.print("AccErrorY: ");
  Serial.println(AccErrorY);
  Serial.print("GyroErrorX: ");
  Serial.println(GyroErrorX);
  Serial.print("GyroErrorY: ");
  Serial.println(GyroErrorY);
}

void read_IMU() {
  // === Read acceleromter data === //
  Wire.beginTransmission(MPU);
  Wire.write(0x3B); // Start with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 6, true); // Read 6 registers total, each axis value is stored in 2 registers
  //For a range of +-8g, we need to divide the raw values by 4096, according to the datasheet
  AccX = (Wire.read() << 8 | Wire.read()) / 4096.0; // X-axis value
  AccY = (Wire.read() << 8 | Wire.read()) / 4096.0; // Y-axis value
  AccZ = (Wire.read() << 8 | Wire.read()) / 4096.0; // Z-axis value

  // Calculating angle values using
  accAngleX = (atan(AccY / sqrt(pow(AccX, 2) + pow(AccZ, 2))) * 180 / PI) + 1.15; // AccErrorX ~(-1.15) See the calculate_IMU_error()custom function for more details
  accAngleY = (atan(-1 * AccX / sqrt(pow(AccY, 2) + pow(AccZ, 2))) * 180 / PI) - 0.52; // AccErrorX ~(0.5)

  // === Read gyro data === //
  previousTime = currentTime;        // Previous time is stored before the actual time read
  currentTime = millis();            // Current time actual time read
  elapsedTime = (currentTime - previousTime) / 1000;   // Divide by 1000 to get seconds
  Wire.beginTransmission(MPU);
  Wire.write(0x43); // Gyro data first register address 0x43
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 4, true); // Read 4 registers total, each axis value is stored in 2 registers
  GyroX = (Wire.read() << 8 | Wire.read()) / 32.8; // For a 1000dps range we have to divide first the raw value by 32.8, according to the datasheet
  GyroY = (Wire.read() << 8 | Wire.read()) / 32.8;
  GyroX = GyroX + 1.85; //// GyroErrorX ~(-1.85)
  GyroY = GyroY - 0.15; // GyroErrorY ~(0.15)
  // Currently the raw values are in degrees per seconds, deg/s, so we need to multiply by sendonds (s) to get the angle in degrees
  gyroAngleX = GyroX * elapsedTime;
  gyroAngleY = GyroY * elapsedTime;

  // Complementary filter - combine acceleromter and gyro angle values
  angleX = 0.98 * (angleX + gyroAngleX) + 0.02 * accAngleX;
  angleY = 0.98 * (angleY + gyroAngleY) + 0.02 * accAngleY;
  // Map the angle values from -90deg to +90 deg into values from 0 to 255, like the values we are getting from the Joystick
  data.j1PotX = map(angleX, -90, +90, 255, 0);
  data.j1PotY = map(angleY, -90, +90, 0, 255);
}

Receiver Code:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(8, 10);   // nRF24L01 (CE, CSN)

const byte address[6] = "00001";

unsigned long lastReceiveTime = 0;
unsigned long currentTime = 0;

boolean connection = true;

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte j1PotX;
  byte j1PotY;
  byte j1Button;
  byte j2PotX;
  byte j2PotY;
  byte j2Button;
  byte pot1;
  byte pot2;
  byte tSwitch2;
};

Data_Package data; //Create a variable with the above structure

void setup() {
  Serial.begin(9600);
  Serial.println("");
  Serial.println("");
  if (radio.begin()){
    Serial.println("RF started successfully!");
  }
  else {
    Serial.println("RF start failed!");
  }
  radio.openReadingPipe(0, address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);
  radio.startListening(); //  Set the module as receiver
  resetData();
}
void loop() {
  // Check whether there is data to be received
  if (radio.available()) {
    radio.read(&data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure
    Serial.println("Radio received");
    connection = true;
    lastReceiveTime = millis(); // At this moment we have received the data
  }
  // Check whether we keep receving data, or we have a connection between the two modules
  currentTime = millis();
  if ( currentTime - lastReceiveTime > 1000 ) { // If current time is more then 1 second since we have recived the last data, that means we have lost connection
    resetData(); // If connection is lost, reset the data. It prevents unwanted behavior, for example if a drone has a throttle up and we lose connection, it can keep flying unless we reset the values
    connection = false;
  }
  if (connection == true){
    // Print the data in the Serial Monitor
    Serial.print("j1PotX: ");
    Serial.print(data.j1PotX);
    Serial.print("; j1PotY: ");
    Serial.print(data.j1PotY);
    Serial.print("; button2: ");
    Serial.print(data.tSwitch2);
    Serial.print("; pot1: ");
    Serial.println(data.pot1);
  }
}

void resetData() {
  // Reset the values when there is no radio connection - Set initial default values
  data.j1PotX = 127;
  data.j1PotY = 127;
  data.j2PotX = 127;
  data.j2PotY = 127;
  data.j1Button = 1;
  data.j2Button = 1;
  data.pot1 = 0;
  data.pot2 = 0;
  data.tSwitch2 = 1;
}

The code is mostly from these websites:

Anyone, any ideas? Thanks in advance!

Check out the RF nano. It's an Arduino Nano with an nRF24L01 chip built-in, and it has an antenna port, though you'll need an adapter to make it work with your SMA antenna.

The issue with it is range.

If you don't want to do that, try following the ever-popular tutorial by Robin2 to get them working. If you wire them right, it should work first try.

Hi @IceChes,

Thanks for the quick response! I am trying to get this to work up to 1,100 meters (3,300 ft), so I decided not to use the RF nano. Ill try the tutorial by Robin2 and see if I can't get this working. Thanks!

One thing to consider is the HC-12, which works well at these huge distances (or so I've heard). If your nRFs don't end up working, they seem like a good place to [re]start.

EDIT - On my combat robot I used RF nanos. Robin's tutorial worked first-try and then I modified the code from the website you listed above to be the main TX/RX system with a full failsafe.

Yeah, the HC-12 is good up to 600 meters which is still really far. I was also thinking of using RA-02, which is LORA. Those distances can be from 500 - 100,000 meters.

When considering UHF 433Mhz devices do research the legal limits on duty cycle in your area.

For a lot of the UHF LoRa devices a duty cycle limit of 10% applies, which may not be enough for a continuous transmit application like RC.

The SX126X UHF LoRa devices can operate reliably at a low enough bandwidth to permit 100% duty cycle in some places.

Or just forget about duty cycle limits and use the 2.4Ghz LoRa devices, with no duty cycle limits in that band.

1 Like

The high-power modules will not work reliably without a solid 3.3V supply, just saying.

1 Like

Yes, it appears OP is using the 3.3v pin.

@kgray9 The 3.3v pin supplies more like ~2.9v, so use your 5v pin with a 3.3v regulator.

Nanos (I think it is one in the picture) get their 3.3V from the flimsy USB to serial converter.
I have never seen a high-power module work from 3.3V from a Nano.

Ok, trying it right now.

The high-power modules also don't work very well close together,
especially in the high-power modes.

You seem to run your sketches with the library default of 2M bit and MAX power (IIRC).

When testing, I have them ~3 feet apart. I thought I was using low power mode "radio.setPALevel(RF24_PA_LOW);" with that in setup.

I just tried using a LD33V instead and attached a 10ยตF capacitor between 3.3V and GND. Still nothing.

I'm about out of advice.

Try changing around CE/CSN pins, modifying your address (noise), using arrays instead of structs (worked for me), changing your data rate, etc. If that doesn't help, get in contact with someone that has more nRF experience than I do.

With some of the +PA+LNA modules they are very close to max power regardless of the level set by radio.setPALevel().

Sorry, I did not look carefully enough.

Oh. Is three feet far enough apart for testing?

Does the behavior change if you bring them further apart?

Probably.

Ummm. It just started working and then stopped again. I just read ~50 packets without changing any code, but then stopped.