Set Throttle to 0 for Standard Analog Joystick on Multiwii + NRF24 Drone TX

Hi all, glad to enter this awesome forum again. Now I have a question. I had tried many times but I get failed.

CASE:
I've been building a NRF24 Transmitter for my Multiwii 2.3 + NRF24 + BMP180 Drone. Everything looks okay, but when I want to test the NRF24L01 transmitter, I see from Serial Monitor and Mini LCD, It shows 127/128 value for Throttle. Therefore, when I arm the drone and, as soon as I release the left stick (it goes to middle-center position = throttle 127/128), the motors start spin 50% or 127/128.

This is what makes it 127/128:

data.throttle  = mapJoystickValues(analogRead(A0), 12, 515, 1024, true );

And, this is to config the mapjoystickValue:

int mapJoystickValues(int val, int lower, int middle, int upper, bool reverse){
  val = constrain(val, lower, upper);
  if ( val < middle )
    val = map(val, lower, middle, 0, 128);
  else
    val = map(val, middle, upper, 128, 255);
  return ( reverse ? 255 - val : val );
}

But, I have no clue, how to modify it.

WHAT TO ACHIEVE:
I want the throttle value go to 0% or 0 or 1 or 10 (even though the left stick is at middle-center position.

Here's the NRF24 Drone code Receiver (related to this case):

1. NRF24_RX.cpp

  nrf24_rcData[THROTTLE]  = map(strData.throttle, 0, 255, 1000, 2000);
  nrf24_rcData[ROLL]      = map(strData.roll,     0, 255, 1000, 2000);
  nrf24_rcData[PITCH]     = map(strData.pitch,    0, 255, 1000, 2000);
  nrf24_rcData[YAW]       = map(strData.yaw,      0, 255, 1000, 2000);
  nrf24_rcData[AUX1]      = map(strData.AUX1,     0, 1,   1000, 2000);
  nrf24_rcData[AUX2]      = map(strData.AUX2,     0, 1,   1000, 2000);  

2. in RX.cpp file:

  #elif defined(NRF24_RX)
    if (chan < RC_CHANS) {
      data = nrf24_rcData[chan];
    } else data = 1500;

FULL CODE for NRF24 TX:

/*
 * https://www.youtube.com/@ardujimmy
 */

/******************************************************* Multiwii 2.3 NRF24L01 Transmitter ********************************************************/
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include <SPI.h>
#include <nRF24L01.h>  
#include <RF24.h>              
  
const uint64_t pipeOut = 0xE8E8F0F0E1LL; //IMPORTANT: The same as in the receiver!!!

RF24 radio(4, 3); // CE, CSN

String strAUX1;

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDR 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

int16_t tRoll;
int16_t tPitch;

#define trimRollUpPin 8
#define trimRollDownPin 7
#define trimPitchUpPin 6
#define trimPitchDownPin 5

struct MyData {
  byte throttle;
  byte yaw;
  byte pitch;
  byte roll;
  byte AUX1;
  int16_t rollTrim;
  int16_t pitchTrim;
};

MyData data;

void resetData() {
  data.throttle = 0;
  data.yaw = 127;
  data.pitch = 127;
  data.roll = 127;
  data.AUX1 = 0;
  data.rollTrim = 0;
  data.pitchTrim = 0;
}

void setup(){
  Serial.begin(9600);  
  radio.begin();
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.openWritingPipe(pipeOut);

  for (byte address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    if (Wire.endTransmission() == 0) {
      Serial.print("I2C device found at 0x");
      Serial.println(address, HEX);
    }

    if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
      Serial.println(F("SSD1306 allocation failed"));
      //for (;;)
        ;
    }

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  //Show in LCD
  display.clearDisplay();
  display.setCursor(0, 0); // Set cursor to top-left corner
  display.println("Hallo Ardujimmy");
  display.println("-----------------");      
  display.println("Please wait .....");       
  display.display();
  delay(100);   
  pinMode(trimRollUpPin, INPUT_PULLUP);
  pinMode(trimRollDownPin, INPUT_PULLUP);
  pinMode(trimPitchUpPin, INPUT_PULLUP);
  pinMode(trimPitchDownPin, INPUT_PULLUP);
  resetData();    
  }
}

void updateTrimValues() {
  if (digitalRead(trimRollUpPin) == HIGH) {
    tRoll += 5;  // Increase roll trim
  }
  if (digitalRead(trimRollDownPin) == HIGH) {
    tRoll -= 5;  // Decrease roll trim
  }
  if (digitalRead(trimPitchUpPin) == HIGH) {
    tPitch += 5; // Increase pitch trim
  }
  if (digitalRead(trimPitchDownPin) == HIGH) {
    tPitch -= 5; // Decrease pitch trim
  }
  // Constrain trim values
  tRoll = constrain(tRoll, -250, 250);
  tPitch = constrain(tPitch, -250, 250);
}

int mapJoystickValues(int val, int lower, int middle, int upper, bool reverse){
  val = constrain(val, lower, upper);
  if ( val < middle )
    val = map(val, lower, middle, 0, 128);
  else
    val = map(val, middle, upper, 128, 255);
  return ( reverse ? 255 - val : val );
}

void loop(){
      updateTrimValues();
      //data.throttle  = mapJoystickValues(analogRead(A0), 12, 515, 1024, true );
      data.throttle  = mapJoystickValues(analogRead(A0), 12, 515, 1024, true );
      data.yaw       = mapJoystickValues(analogRead(A1),  13, 524, 1024, true ); 
      data.pitch     = mapJoystickValues(analogRead(A3), 10, 512, 1024, true );
      data.roll      = mapJoystickValues(analogRead(A2),  1, 524, 1022, true );
      data.AUX1      = digitalRead(9);
      data.rollTrim  = tRoll;
      data.pitchTrim = tPitch;
      //Send all
      radio.write(&data, sizeof(MyData));
      //Show in LCD
      display.clearDisplay();
      display.setCursor(0, 0); // Set cursor to top-left corner
      display.println("NRF24 BMP180 QuadX");
      if (data.AUX1 == 1){
          strAUX1 = "AUX1 ON";
          }
       else{
          strAUX1 = "AUX1 OFF";
          }
      display.println(strAUX1);          
      display.println("Throttle: " + String(data.throttle));
      display.println("Yaw     : " + String(data.yaw));
      display.println("Roll    : " + String(data.roll));
      display.println("Pitch   : " + String(data.pitch));
      display.println("t_Pitch : " + String(data.pitchTrim));
      display.println("t_Roll  : " + String(data.rollTrim));
      display.display();
      // Add delay for readability
      delay(100);
   }

Perhaps necessary:

Source: https://www.youtube.com/shorts/cfkqHvwNXHw

Analog Joystick I use now:

What happens to val when middle = 128?
Where is the original code (not the code you posted)?

motor starts spinning as soon as drone armed. Original code is for arduino car where the position of throt val is 127 or 128. I never find the code for NRF24 drone TX (related to the throt issue).

I found Nrf24 drone TX from electronoobs. I did the same with joystick who stays at bottom position or lowest position, so no change needed since the val of throt is zero.

OK, that was confusing, as I was reading your slash as division.

Typically the throttle stick is without spring return to zero. I suggest you to use a better joystick, for many reasons, and a better joystick will easily be modified to no return to the midpoint.

But let's say you don't wanna or can't. What then shoukd be the interpretation of the full range of the throttle axis?

If the midpoint is zero, do you want zero throttle from there down? This cuts you control resolution in half. Speaking as a free styler and racer this would be unacceptable, but certainly possible.

Or are you with a vehicle that has some kind of altitude hold, where the midpoint should mean hover, increasing means ascend at some speed increasing to a max, and below midpoint mean descend at some speed increasing to a max?

Or is it a helicopter?

We can fix the mapping in various ways, but I'm too lazy to do until you are clear about your goal and realize the compromise you might be making in attaining it.

a7

I understand, but you have "128" as min and max values of your function.

128 min is not defined as min throt (by the receiver) but middle val. Of course, it starts spinning, bro

I did with proper joystick, e.g. flysky joystick. and it works as expected without changing anything of the code. Now the issue is that I use standard (prototype as in the pic I posted) analog joystick

that's not helicopter, it's a QuadX. Here's the flying demo:

(in that flying test, I use joystick like flysky (or as you said "proper) and now I use standard prototype analog joystick in this project

When either is at the midpoint, they should both be reporting raw values around 512.

Post a simple sketch where your real joystick shows a significant difference to your toy joystick.

Just analog read the pot and show a difference. Just wire the wiper ends to Vcc and GND.

Or explain yourself better - with the spring return to midpoint, you are asking for 50 percent throttle. How should it work instead?

And why, if you have real joysticks, would you ever bother to even try to use the cheap ones? Trust me, flying is hard enough without using crapsticks.

I buy toys and basically throw away the ridiculable "controller".

a7

Okay, this is working code for "proper" joystick:

#include <SPI.h>
#include <nRF24L01.h>  
#include <RF24.h>              
  
const uint64_t pipeOut = 0xE8E8F0F0E1LL; //IMPORTANT: The same as in the receiver!!!

RF24 radio(4, 3); // CE, CSN

struct MyData {
  byte throttle;
  byte yaw;
  byte pitch;
  byte roll;
  byte AUX1;
  byte AUX2;
};
MyData data;

void resetData() {
  data.throttle = 0;
  data.yaw = 127;
  data.pitch = 127;
  data.roll = 127;
  data.AUX1 = 0;
  data.AUX2 = 0;
}

void setup(){
  Serial.begin(9600);  
  radio.begin();
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.openWritingPipe(pipeOut);
  resetData();
}

int mapJoystickValues(int val, int lower, int middle, int upper, bool reverse){
  val = constrain(val, lower, upper);
  if ( val < middle )
    val = map(val, lower, middle, 0, 128);
  else
    val = map(val, middle, upper, 128, 255);
  return ( reverse ? 255 - val : val );
}

void loop(){
  data.throttle = mapJoystickValues(analogRead(A1), 10, 512, 1024, true ); 
  data.yaw      = mapJoystickValues(analogRead(A0), 12, 515, 1024, true );
  data.pitch    = mapJoystickValues(analogRead(A3), 10, 512, 1024, true );
  data.roll     = mapJoystickValues(analogRead(A2), 1, 524, 1022, true );
  data.AUX1     = digitalRead(5);
  data.AUX2     = digitalRead(6);
  radio.write(&data, sizeof(MyData));
}

You're absolutely correct about using proper joystick (not a toy). But my subscribers (about 90% use toy joystick LOL. I realize I'm still beginnner forever in this case).

While asking a question here, I'm still trying to modify the code if it can solve.

Please say how you want the cheap joystick to work.

If you leave the spring return to middle in place, what shoukd mean the entire range of that axis?

I can't ask any better, please read my responses carefully, it amounts to: Do you intend to leave the bottom half of the range unused?

As for your peeps, I wish them luck. I gave toys to friends not realizing the tremendous advantage one has when using a real controller, had to wonder why they didn't enjoy flying until I tried myself with only what I had given them…

a7

What I want is that the value of min throttle is not 127 but 0 (at the middle-center stick position).

(Of course from middle center to the bottom will have no any values or unused).

OK, so I am waiting for my ride to the beach, but confirm:

Any joystick now feeds 0 .. 255 over to the flight control software. Or at some point in the chain it is in that range, which comes due to full travel in the stick.

So for you toy joystick, you want 0 to halfway to report 0 (no throttle) and from halfway to full it shoukd report 0 .. 255.

I'll take what time I have to look into it. Glancing at the code suggests this should be easy. I'm sure that even without knowing the details, someone might spot where to place a fork and knife and poss a spoon in the code to force that to be the case.

One thing to know about the map() function is that it will happily extrapolate, which is why it often appears close to calls to another useful function constrain(), which can be employed to keep values in the r angle where they have meaning.

L8R

a7

1 Like

wait, okay. I will have a go at it again.

1. You said about range based on the joystic characteristic:

  • min throttle: 0 (not 127)
  • mid throttle: 192
  • max throttle: 255

2. You said about constrain() and map() and I think I must re-write the code specifically for the THROTTLE. Here it is:

int mapThrottleValues(int Throtval, int lower, int middle, int upper, bool reverse){
  Throtval = constrain(Throtval, lower, upper);
  if ( Throtval < middle )
    Throtval = map(Throtval, lower, middle, 0, 192);
  else
    Throtval = map(Throtval, middle, upper, 192, 255);
  return ( reverse ? 255 - Throtval : Throtval );
}

3. You said about "where they have meaning" and I decide to change the data.throttle in void loop() as follows:

data.throttle = mapThrottleValues(analogRead(A0), 2, 128, 518, true );

DONE SUCCESSFULLY and Tested!

Here's the screenshot!


s

I also push down the stick and it gives me 0 value.
Then pushing up to the max throttle, it gives me 255 value.

Finally, the motors not spinning again as soon as I arm and starts spinning when I give throttle up from 2 to 255. :smiling_face_with_three_hearts: :kissing_heart:
Thanks sooooooo muacch!

Brilliant, good. I was busy eating, I don't like to drink on an empty stomach.

One odd number you wrote was 192, which I do recognize as halfway between 256 and 128, so I'll figure that out on my own, NP.

Because… I do not argue with success. Happy flying!

a7

1 Like

yes, I got the idea when you said about range LOL. Aw! now time's to make a video of flying test to train barometer module (bmp180) using the trimmers (pitch and roll) for NRF24L01 Multiwii 2.3 QuadX with BMP180 to my subscribers :smiling_face_with_three_hearts:

Have a great day! and happy eating. Sorry to disturb you hahaha

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.