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?