Map() function data types

Hello I recently built a Nano+nrf2401 transmitter and Nano+nrf2401 receiver to use on a quadcopter drone. The joystick trimpot data is transmitted to the receiver using a structure of bytes to hold the various data points from transmitter. The transmitter code is below

//Include libraries needed for all communication
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>
#include "MPU6050.h"

// Define the digital inputs
#define t1 5   // Toggle switch 1
#define t2 4   // Toggle switch 2
#define b1 2   // Button 1
#define b2 3   // Button 2
#define b3 6   // Button 3
#define b4 9   // Button 4
#define l1 10  // External LED (blue)

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;
bool writeStatus; //True if RF24 was able to send Data_package to receiver


RF24 radio(7, 8, 1000000);   // 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 j2PotX;
  byte j2PotY;
  byte pot1;
  byte pot2;
  byte tSwitch1;
  byte tSwitch2;
  byte button1;
  byte button2;
  byte button3;
  byte button4;
};

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

void setup() {
  Serial.begin(9600);
   
  //Initialize interface to 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
  radio.begin();
  if (!radio.begin()) {
        Serial.println(F("radio hardware not responding!"));
        //while (1) {} // hold program in infinite loop to prevent subsequent errors
       }
  if (!radio.isChipConnected()) {
        Serial.println(F("Chip not connected to SPI bus!"));
   }
  radio.openWritingPipe(address);
  //radio.setAutoAck(true);
  //radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MIN,0);
  radio.stopListening();

  // Activate the Arduino internal pull-up resistors
  pinMode(t1, INPUT_PULLUP);
  pinMode(t2, INPUT_PULLUP);
  pinMode(b1, INPUT_PULLUP);
  pinMode(b2, INPUT_PULLUP);
  pinMode(b3, INPUT_PULLUP);
  pinMode(b4, INPUT_PULLUP);

}

void loop() {
  // Read all analog inputs and map them to one Byte value
  data.j1PotX = map(analogRead(A1), 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(A0), 0, 1023, 0, 255);
  data.j2PotX = map(analogRead(A2), 0, 1023, 0, 255);
  data.j2PotY = map(analogRead(A3), 0, 1023, 0, 255);
  data.pot1 = map(analogRead(A7), 0, 1023, 0, 255);
  data.pot2 = map(analogRead(A6), 0, 1023, 0, 255);
  
  // Read all digital inputs
  data.tSwitch1 = digitalRead(t1); //D5  Toggle switch 1
  data.tSwitch2 = digitalRead(t2); //D4  Toggle switch 2
  data.button1 = digitalRead(b1); //D2   Button 1
  data.button2 = digitalRead(b2); //D3   Button 2
  data.button3 = digitalRead(b3); //D6   Button 3
  data.button4 = digitalRead(b4); //D9   Button 4
  
  // Send the whole data from the structure to the receiver
  radio.write(&data, sizeof(Data_Package));
  
  if (radio.write(&data, sizeof(Data_Package))) {
    digitalWrite(l1,HIGH);
  }
  else {
    digitalWrite(l1,LOW);
    //Serial.println("Payload sent but ack packet not received");
  }
  

  if (data.tSwitch1 == 0) {
    read_IMU();    // Use MPU6050 instead of Joystick 1 for controling left, right, forward and backward movements
  }

  
  //Serial.println(data.tSwitch1);
  //Serial.println(radio.write(&data, sizeof(Data_Package)));
  //Serial.println(radio.isChipConnected());

    Serial.print("data.j1PotY = ");
    Serial.print(data.j1PotY);
    Serial.print("; data.j1PotX = ");
    Serial.print(data.j1PotX);
    //Serial.print("data.j2PotX = ");
    //Serial.println(data.j2PotX);
    //Serial.print("data.j2PotY = ");
    //Serial.println(data.j2PotY);
   //Serial.print("data.button4 = ");
   // Serial.println(data.button4);
     Serial.print("; data.tSwitch1 = ");
    Serial.println(data.tSwitch1);
  //Serial.println();
  //Serial.println();
  
  //Serial.print();
  //Serial.print();
    //delay(3000);

}

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 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);
}

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);
}

The receive code is below

//Include libraries needed for all communication
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>
//#include <Wire.h>
//#include "MPU6050.h"

RF24 radio(7, 8, 1000000);   // nRF24L01 (CE, CSN)
Servo topLeftESC; //Create topLeftESC servo object

#define TopLeft_ESC 3 //D3 (PWM) to top left ESC
#define BottomLeft_ESC 5 //D5 (PWM) to bottom left ESC
#define BottomRight_ESC 6 //D6 (PWM) to bottom right ESC
#define TopRight_ESC 9 //D9 (PWM) to top right ESC

const byte address[6] = "00001";

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

int TopLeft_pwmVal, BottomLeft_pwmVal, BottomRight_pwmVal, TopRight_pwmVal;

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte j1PotX;
  byte j1PotY;
  byte j2PotX;
  byte j2PotY;
  byte pot1;
  byte pot2;
  byte tSwitch1;
  byte tSwitch2;
  byte button1;
  byte button2;
  byte button3;
  byte button4;
};

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

void setup() {
  
  topLeftESC.attach(3); //Top left ESC signal pin connected to D3
  topLeftESC.writeMicroseconds(1000); //Initialize top left ESC signal to minimum throttle signal 1000us

  //Start serial communication at 9600 baud
  Serial.begin(9600);

  // Define the radio communication
  radio.begin();

  if (!radio.begin()) {
        Serial.println(F("radio hardware not responding!"));
        //while (1) {} // hold program in infinite loop to prevent subsequent errors
   }
  if (!radio.isChipConnected()) {
        Serial.println(F("Chip not connected to SPI bus!"));
   }
  radio.openReadingPipe(0, address);
  //radio.setAutoAck(true);
  //radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MIN,0);
  radio.startListening(); //  Set the module as receiver

  
  // Reset the values when there is no radio connection - Set initial default values
  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
    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
  //}

  TopLeft_pwmVal = map(data.j1PotY,0,255,1000,2000); //Map left joystick y coordinate (0-255) byte to int value (1000-2000)
  //BottomLeft_pwmVal = map(data.j1PotY,0,255,1000,2000);
  //BottomRight_pwmVal = map(data.j1PotY,0,255,1000,2000);
  //TopRight_pwmVal = map(data.j1PotY,0,255,1000,2000);

  topLeftESC.writeMicroseconds(TopLeft_pwmVal); //Write mapped left joystick y coordiate value to top left ESC

  // Print the data in the Serial Monitor
  Serial.print("j1PotY: ");
  Serial.print(data.j1PotY);
  Serial.print("; j1PotX: ");
  Serial.print(data.j1PotX);
  Serial.print("; TopLeft_pwmVal: ");
  Serial.println(TopLeft_pwmVal);
  //Serial.print("; j2PotX: ");
  //Serial.println(data.j2PotX); 
  //delay(3000);
}

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.pot1 = 1;
  data.pot2 = 1;
  data.tSwitch1 = 1;
  data.tSwitch2 = 1;
  data.button1 = 1;
  data.button2 = 1;
  data.button3 = 1;
  data.button4 = 1;
}




Right now I have only been playing with the top left motor data just to make sure I'm transmitting and receiving correctly. On the receive side, I use the map function to map joystick 1 y-axis byte data to a pwm throttle number from 1000-2000 that the motor ESC will understand. It works great but after doing this I noticed the variable I use to store the mapped ESC data (TopLeft_pwmVal) is an int and the joystick data is a byte. My question is does the map() function take any input data type and transform it to the data type you chose? In my case I declared TopLeft_pwmVal an int

int TopLeft_pwmVal, BottomLeft_pwmVal, BottomRight_pwmVal, TopRight_pwmVal;

then I called map() in the following way

 TopLeft_pwmVal = map(data.j1PotY,0,255,1000,2000); //Map left joystick y coordinate (0-255) byte to int value (1000-2000)

The documentation on map() says it returns a long. Also is long still considered an integer even though int is 16 bits and long is 32 bits?

Here's the map function.

long map(long x, long in_min, long in_max, long out_min, long out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

C/C++ promotes arguments in calls, so it works with 16 bit or 8 bit integers.

And integer in any case means the same thing as it did in maths class, you know, whole numbers, natural numbers, integers, real numbers &c.

a7

@alto777 thank you for the reply. Can you elaborate on the following statement

C/C++ promotes arguments in calls, so it works with 16 bit or 8 bit integers.

I know this has to do with why it works when I pass it the byte data.j1potY but I'm not fully following the statement

this whole can be 1 byte, actually 6 bits of 1 byte

if you agree to swap D9 on pin D7 then whole 6 bits of buttons states can be read in one step:
byte States = PIND & 0xFC; // B11111100 -- 7,6,5,4,3,2 pins

See

It's not too complicated, from a common sense point of view, it "just works" as you might expect and hope.

a7

Trans

//Include libraries needed for all communication
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>
#include "MPU6050.h"

// Define the digital inputs
#define t1 5   // Toggle switch 1
#define t2 4   // Toggle switch 2
#define b1 2   // Button 1
#define b2 3   // Button 2
#define b3 6   // Button 3
#define b4 7   // Button 4          << ------      **pin changed**
#define l1 10  // External LED (blue)

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;

RF24 radio(8, 9, 1000000);   // nRF24L01 (CE, CSN) <<------------------------ **changed**
#define address "00001" // Address

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

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

void setup() {
  Serial.begin(9600);

  //Initialize interface to MPU6050
  initialize_MPU6050();

  // Define the radio communication
  radio.begin();
  if (!radio.begin()) {
    Serial.println(F("radio hardware not responding!"));
    //while (1) {} // hold program in infinite loop to prevent subsequent errors
  }
  if (!radio.isChipConnected()) {
    Serial.println(F("Chip not connected to SPI bus!"));
  }
  radio.openWritingPipe(address);
  //radio.setAutoAck(true);
  //radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MIN, 0);
  radio.stopListening();

  // Activate the Arduino internal pull-up resistors
  pinMode(t1, INPUT_PULLUP);
  pinMode(t2, INPUT_PULLUP);
  pinMode(b1, INPUT_PULLUP);
  pinMode(b2, INPUT_PULLUP);
  pinMode(b3, INPUT_PULLUP);
  pinMode(b4, INPUT_PULLUP);
}

void loop() {
  // Read all digital inputs in one go
  data.States = PIND & 0xF3;


  if (data.States & 0x20) {
    // Read all analog inputs 
    data.j1PotX = analogRead(A1);
    data.j1PotY = analogRead(A0);
    data.j2PotX = analogRead(A2);
    data.j2PotY = analogRead(A3);
    data.pot1 = analogRead(A7);
    data.pot2 = analogRead(A6);

  } else read_IMU();// Use MPU6050 instead of Joystick 1 for controling left, right, forward and backward movements
  // Send the whole data from the structure to the receiver
  digitalWrite(l1, radio.write(&data, sizeof(Data_Package)));

  Serial.print("PotY = ");
  Serial.print(data.j1PotY);
  Serial.print("; PotX = ");
  Serial.print(data.j1PotX);

  Serial.print("; tSwitch1 is ");
  Serial.println((data.States & 0x20) ? "not active" : "Activated");
  Serial.flush();
  delay(10);
}

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 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, 2000, 1000);
  data.j1PotY = map(angleY, -90, +90, 1000, 2000);
}

Recv:

//Include libraries needed for all communication
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>

RF24 radio(7, 8, 1000000);   // nRF24L01 (CE, CSN)
Servo topLeftESC; //Create topLeftESC servo object

#define TopLeft_ESC 3 //D3 (PWM) to top left ESC
#define address  "00001"

struct Data_Package {
  int j1PotX;
  int j1PotY;
  int j2PotX;
  int j2PotY;
  int pot1;
  int pot2;
  byte States;
}data; //Create a variable with the above structure

void setup() {
  topLeftESC.attach(TopLeft_ESC); //Top left ESC signal pin connected to D3
  topLeftESC.writeMicroseconds(1000); //Initialize top left ESC signal to minimum throttle signal 1000us

  //Start serial communication at 9600 baud
  Serial.begin(9600);

  // Define the radio communication
  radio.begin();

  if (!radio.begin()) {
    Serial.println(F("radio hardware not responding!"));
    //while (1) {} // hold program in infinite loop to prevent subsequent errors
  }
  if (!radio.isChipConnected()) {
    Serial.println(F("Chip not connected to SPI bus!"));
  }
  radio.openReadingPipe(0, address);
  //radio.setAutoAck(true);
  //radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MIN, 0);
  radio.startListening(); //  Set the module as receiver

  // Reset the values when there is no radio connection - Set initial default values
  resetData();
}

void loop() {
  if (radio.available()) {
    radio.read(&data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure
  }

  topLeftESC.writeMicroseconds(data.j1PotY); //Write mapped left joystick y coordiate value to top left ESC

  Serial.print("j1PotY: ");
  Serial.print(data.j1PotY);
  Serial.print("; j1PotX: ");
  Serial.print(data.j1PotX);
  Serial.print("; TopLeft_pwmVal: ");
  Serial.println(data.j1PotY);
  Serial.flush();
  delay(50);
}
void resetData() {
  // Reset the values when there is no radio connection - Set initial default values
  data.j1PotX = 511;
  data.j1PotY = 511;
  data.j2PotX = 511;
  data.j2PotY = 511;
  data.pot1 = 1;
  data.pot2 = 1;
  data.States = B111111;
}

@kolaha Thanks for this tip. Do you think this would improve performance of transmit/receive process or would it be negligiblke compared to bit rate? I believe I read bit rate of RF24 was 250kbps if not set to something else. I ended up commenting out calling the setDataRate method because it seemed to work better when I did

radio.openReadingPipe(0, address);
  //radio.setAutoAck(true);
  //radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MIN,0);
  radio.startListening(); //  Set the module as receiver

Is this statment using a bitmask to find out which bits are 1 or 0?

byte States = PIND & 0xFC; // B11111100 -- 7,6,5,4,3,2 pins

@alto777 great article. It explained what was happening very clearly. thanks

Hello I recently built a Nano+nrf2401 transmitter with 2 joysticks and nano+nrf2401 reciver which is mounted on quadcopter drone. This is my first time building a drone. Everything is communicating well and I have all four motors running with ESCs. The only operation I currently have is the left joystick y-axis engages all four motors for vertical lift. I'm at the point now where I need to incorporate other joystick movements into drone movements. For example, I move left joystick up a bit and now drone is hovering at some alltitude. Now if I moved x-axis of left joystick how should this affect position of drone. Also the right joystick x,y axis.

Right now I only have the y axis of left joystick translated into all four motors engaging for a vertical lift. Any help with translating other joystick movements into drone movements would be greatly appreciated. Forgive me if this isn't the right channel for this. It looked like the closest category for what I'm doing.

The transmitter code is below

//Include libraries needed for all communication
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>
#include "MPU6050.h"

// Define the digital inputs
#define t1 5   // Toggle switch 1
#define t2 4   // Toggle switch 2
#define b1 2   // Button 1
#define b2 3   // Button 2
#define b3 6   // Button 3
#define b4 9   // Button 4
#define l1 10  // External LED (blue)

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;
bool writeStatus; //True if RF24 was able to send Data_package to receiver
uint8_t PALevel; //Current power amplifier level of NRF2401


RF24 radio(7, 8, 1000000);   // 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 j2PotX;
  byte j2PotY;
  byte pot1;
  byte pot2;
  byte tSwitch1;
  byte tSwitch2;
  byte button1;
  byte button2;
  byte button3;
  byte button4;
};

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

void setup() {
  Serial.begin(9600);
   
  //Initialize interface to 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
  radio.begin();
  if (!radio.begin()) {
        Serial.println(F("radio hardware not responding!"));
        //while (1) {} // hold program in infinite loop to prevent subsequent errors
       }
  if (!radio.isChipConnected()) {
        Serial.println(F("Chip not connected to SPI bus!"));
   }
  radio.openWritingPipe(address);
  //radio.setAutoAck(true);
  //radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_HIGH,0);
  PALevel = radio.getPALevel();
  Serial.print("PA level = ");
  Serial.println(PALevel);
  radio.stopListening();

  // Activate the Arduino internal pull-up resistors
  pinMode(t1, INPUT_PULLUP);
  pinMode(t2, INPUT_PULLUP);
  pinMode(b1, INPUT_PULLUP);
  pinMode(b2, INPUT_PULLUP);
  pinMode(b3, INPUT_PULLUP);
  pinMode(b4, INPUT_PULLUP);

}

void loop() {
  // Read all analog inputs and map them to one Byte value
  data.j1PotX = map(analogRead(A1), 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(A0), 0, 1023, 0, 255);
  data.j2PotX = map(analogRead(A2), 0, 1023, 0, 255);
  data.j2PotY = map(analogRead(A3), 0, 1023, 0, 255);
  data.pot1 = map(analogRead(A7), 0, 1023, 0, 255);
  data.pot2 = map(analogRead(A6), 0, 1023, 0, 255);
  
  // Read all digital inputs
  data.tSwitch1 = digitalRead(t1); //D5  Toggle switch 1
  data.tSwitch2 = digitalRead(t2); //D4  Toggle switch 2
  data.button1 = digitalRead(b1); //D2   Button 1
  data.button2 = digitalRead(b2); //D3   Button 2
  data.button3 = digitalRead(b3); //D6   Button 3
  data.button4 = digitalRead(b4); //D9   Button 4
  
  // Send the whole data from the structure to the receiver
  radio.write(&data, sizeof(Data_Package));
  
  if (radio.write(&data, sizeof(Data_Package))) {
    digitalWrite(l1,HIGH);
  }
  else {
    digitalWrite(l1,LOW);
    //Serial.println("Payload sent but ack packet not received");
  }
  

  if (data.tSwitch1 == 0) {
    read_IMU();    // Use MPU6050 instead of Joystick 1 for controling left, right, forward and backward movements
  }

  
  //Serial.println(data.tSwitch1);
  //Serial.println(radio.write(&data, sizeof(Data_Package)));
  //Serial.println(radio.isChipConnected());

    Serial.print("data.j1PotY = ");
    Serial.print(data.j1PotY);
    Serial.print("; data.j1PotX = ");
    Serial.print(data.j1PotX);
    //Serial.print("data.j2PotX = ");
    //Serial.println(data.j2PotX);
    //Serial.print("data.j2PotY = ");
    //Serial.println(data.j2PotY);
   //Serial.print("data.button4 = ");
   // Serial.println(data.button4);
     Serial.print("; data.tSwitch1 = ");
    Serial.println(data.tSwitch1);
  //Serial.println();
  //Serial.println();
  
  //Serial.print();
  //Serial.print();
    //delay(3000);

}

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 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);
}

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);
}

The receiver code is below

//Include libraries needed for all communication
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>
//#include <Wire.h>
//#include "MPU6050.h"

RF24 radio(7, 8, 1000000);   // nRF24L01 (CE, CSN)
Servo topLeftESC; //Create topLeftESC servo object
Servo bottomLeftESC; //Create bottomLeftESC servo object
Servo bottomRightESC; //Create bottomRightESC servo object
Servo topRightESC; //Create bottomRightESC servo object

//#define TopLeft_ESC 3 //D3 (PWM) to top left ESC
//#define BottomLeft_ESC 5 //D5 (PWM) to bottom left ESC
//#define BottomRight_ESC 6 //D6 (PWM) to bottom right ESC
//#define TopRight_ESC 9 //D9 (PWM) to top right ESC

const byte address[6] = "00001";

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

int TopLeft_pwmVal, BottomLeft_pwmVal, BottomRight_pwmVal, TopRight_pwmVal;
uint8_t PALevel; //Current power amplifier level of NRF2401

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte j1PotX;
  byte j1PotY;
  byte j2PotX;
  byte j2PotY;
  byte pot1;
  byte pot2;
  byte tSwitch1;
  byte tSwitch2;
  byte button1;
  byte button2;
  byte button3;
  byte button4;
};

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

void setup() {
  
  topLeftESC.attach(3); //Top left ESC signal pin connected to D3
  bottomLeftESC.attach(5); //Bottom left ESC signal pin connected to D5
  bottomRightESC.attach(6); //Bottom right ESC signal pin connected to D6
  topRightESC.attach(9); //Top right ESC signal pin connected to D9
  
  topLeftESC.writeMicroseconds(1000); //Initialize top left ESC signal to minimum throttle signal 1000us
  bottomLeftESC.writeMicroseconds(1000); //Initialize bottom left ESC signal to minimum throttle signal 1000us
  bottomRightESC.writeMicroseconds(1000); //Initialize bottom right ESC signal to minimum throttle signal 1000us
  topRightESC.writeMicroseconds(1000); //Initialize top right ESC signal to minimum throttle signal 1000us

  //Start serial communication at 9600 baud
  Serial.begin(9600);

  // Define the radio communication
  radio.begin();

  if (!radio.begin()) {
        Serial.println(F("radio hardware not responding!"));
        //while (1) {} // hold program in infinite loop to prevent subsequent errors
   }
  if (!radio.isChipConnected()) {
        Serial.println(F("Chip not connected to SPI bus!"));
   }
  radio.openReadingPipe(0, address);
  //radio.setAutoAck(true);
  //radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_HIGH,0);
  PALevel = radio.getPALevel();
  Serial.print("PA level = ");
  Serial.println(PALevel);
  radio.startListening(); //  Set the module as receiver

  
  // Reset the values when there is no radio connection - Set initial default values
  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
    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
  //}

  TopLeft_pwmVal = map(data.j1PotY,0,255,1000,2000); //Map left joystick y coordinate (0-255) byte to int value (1000-2000)
  BottomLeft_pwmVal = map(data.j1PotY,0,255,1000,2000); //Map left joystick y coordinate (0-255) byte to int value (1000-2000)
  BottomRight_pwmVal = map(data.j1PotY,0,255,1000,2000);
  TopRight_pwmVal = map(data.j1PotY,0,255,1000,2000);

  topLeftESC.writeMicroseconds(TopLeft_pwmVal); //Write mapped left joystick y coordiate value to top left ESC
  bottomLeftESC.writeMicroseconds(BottomLeft_pwmVal); //Write mapped left joystick y coordiate value to bottom left ESC
  bottomRightESC.writeMicroseconds(BottomRight_pwmVal); //Write mapped left joystick y coordiate value to bottom right ESC
  topRightESC.writeMicroseconds(TopRight_pwmVal); //Write mapped left joystick y coordiate value to top right ESC

  // Print the data in the Serial Monitor
  Serial.print("j1PotY: ");
  Serial.print(data.j1PotY);
  Serial.print("; j1PotX: ");
  Serial.print(data.j1PotX);
  Serial.print("; TopLeft_pwmVal: ");
  Serial.println(TopLeft_pwmVal);
  //Serial.print("; j2PotX: ");
  //Serial.println(data.j2PotX); 
  //delay(3000);
}

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.pot1 = 1;
  data.pot2 = 1;
  data.tSwitch1 = 1;
  data.tSwitch2 = 1;
  data.button1 = 1;
  data.button2 = 1;
  data.button3 = 1;
  data.button4 = 1;
}




what evil AI force you to use this sketch at all cost?
is your earlier topic solved?

Standard drone x move is turning the direction of the drone. The right joystick controls forward/backards, sideways meft/right.

@mburdet11

Your two or more topics on the same or similar subject have been merged.

Please do not duplicate your questions as doing so wastes the time and effort of the volunteers trying to help you as they are then answering the same thing in different places.

Please create one topic only for your question and choose the forum category carefully. If you have multiple questions about the same project then please ask your questions in the one topic as the answers to one question provide useful context for the others, and also you won’t have to keep explaining your project repeatedly.

Repeated duplicate posting could result in a temporary or permanent ban from the forum.

Could you take a few moments to Learn How To Use The Forum

It will help you get the best out of the forum in the future.

Thank you.

The next thing is to start using the IMU.

//#include "MPU6050.h"

That you ask what next means you have about zero chance of succeeding.

Many times a second, the current orientation of the quadcopter must be compared to the desired orientation as determined by the joystick input.

Then precise changes to each motor must be calculated to correct any error. Typically this is done using PID control loops so that the errors can be reduced towards zero without oscillation and overshoot.

This is not a simple matter, at all.

I m surprised that you can levitate with no feedback of this nature; you certainly will not be able to fly.

Hit your google and poke around. There are open source quadcopter control "ecosystems", perhaps some reading of the code they use will inform your research and progress.

Quadcopters have no wings, but you def seem to be winging this.

a7

Earlier topic has been solved thank you. Drone is communicating well using map() function to map joystick data into ESC throttle data. Now I'm working on how to map joystick movements into drone movements

I'm familiar with proportional, integral, derivative controllers. I designed a few in school as well as designed all K gain factors for our PCT dryers where I work. Each K factor (Ki, Kp, Kd) determines all your transient and steady state values such as overshoot, steady state error and settling time. You tweek these values to get the desired outcome you're looking for but as in engineering if you want great steady state error you're going to have to give somewhere else.

I've been able to look at the MPU data as well just not sure how to coorelate it with the joystick controls. I started this thing completely from scratch about 2 weeks ago building everything from scratch (transmitter, receiver, drone) so I'm pretty happy with where I'm at with it. I know I have a ways to go but its for fun and I'm defineterly having fun.

Thank you for your advice. I will start looking into the MPU data and how to use it in a pid control loop

You may want to look at @lordmax2's activity on these fora, who seems to have managed the impossible.

Have you flown a quadcopter? Just curious.

Read here

to learn (more) about quadcopter aerodynamics.

The easiest first control system will also be the harder one to fly, or I should say the harder one to learn how to fly, and that is termed "rate orientated" or "acro" mode.

Here, your sticks will be controlling the rate at which the quadcopter is rotating around each of three axes XYZ coordinates fixed to the body.

With the X axis pointing forward, the Y axis to,the left and the Z axis up:

Elevator controls the rate of pitching (rotation about Y), aileron controls the rate of rolling (rotation about the X axis) and rudder controls the rate of yawing (rotation about the Z axis).

Usually the right hand stick does elevator and aileron, and the left hand stick is for rudder and throttle.

The control inputs are the desired rates, the system feedback strives to meet the desire of the pilot.

In the case of something like a camera platform, like a DJI, that control system is not directly answering to the user input. Operating such a quad is usually done in "angle" mode, where the stick input is actually the angle to which the quad should be pitched and rolled; this then informs the rate mode system to make the angles change to meet pilot input.

Such a quad is more controlled by the pilot owner than flown. but the rate mode inner system must be created first.

Once you are competently flying in rate mode, angle mode is (to me) harder. It seems like more work anyway. Any fancy flying you see is undoubtedly rate mode.

Rate mode only needs the gyroscope, as it is only dealing with the motion of the quad.

Angle mode will require using the accelerometer, as now the orientation of the quad is needed in order to develop the error signal which informs the rate mode subsystem.

If you knew all that, you knew. :expressionless:

Did I say to leave the propellers off when they are not needed? You can google some alarming pictures of ppl who did not, and I will admit to being very lucky myself… if I had attached the battery connector firmly, the thing flying towards my face would not have automatically powered down as the juice stopped flowing.

I like my face, and my fingers no less.

Please continue to keep us informed of your progresses, and I hope luck doesn't enter into it, but good luck, too.

a7

Here is what I've done so far in my free time the last couple weeks with couple nanos and breadboard parts. Sorry for just a couple pics but its all it would let me upload. I tried doing a little video but wouldn't let me post. I've got transmitter/receiver circuit working well and pot values converted correctly into ESC throttle values. Now is working on the controls what you're talking about but I guess the good thing is I'm an electrical controls engineer for a living. I do industrial automation and mostly PLCs so my language is ladder logic and structured text but it all compiles the same.

I've already read the datasheet on the MPU6050 so I know how it communicates with the nano via I2C. I read a case where someone didn't use the interrupt pin and waited until FIFO buffer is full and emptied but the way I understood best was using the interrupt pin to signal to nano theres data.

I'm curious why keep referring to this project this way?

who seems to have managed the impossible.

Is it really that bad getting angles from MPU and creating feedback loop for each axis? Haven't many others done this before us? Is it something to do with the Nano that makes this too difficult that I'm missing?

Thanks for all the help so far. I'll keep posted how its going