Joystick range issue (960 - 63929)

Trying to make a button box with buttons, encoders, and potentiometers. Everything is working, but I'm running into an issue with the potentiometers.

For some reason they don't seem to output the full range. The anlogueRead output was 15 through 998, but then randomly when I switched to from "Joystick.Z" to "Joystick.Zrotate", the anlogueRead output range jumped to be 960 through 63929 and then randomly went back to the lower range after I uploaded the code again. ( found out I get the higher numbers when I choose the joystick option without serial)

The major issue right now is that all axes don't seem to be using their full range - I'm not sure how to fix this.


I'm very new to all of this and am very close to being done. Looking for help:)

Additional details
- using windows 10
- using Teensy 4.1


Code:
  //Six rows on pins 0, 1, 2, 3, 4, 5
  //Seven columns on pins 23, 22, 21, 20, 19, 18, 17,
  //Four rotary encoders, pins (33,34) (36,37) (38,39) (40,41)
  //Three potentiometers, pins 24, 25, 26
  
  
    #include <Keypad.h>
    #define JOYSTICK_SIZE 64 // 12 = normal, 64 = extreme joystick
    #include "EncoderTool.h"
    using namespace EncoderTool;
  
  //Button Matrix Section
    const byte ROWS = 6; //four rows
    const byte COLS = 7; //four columns
  
    byte rowPins[ROWS] = {0, 1, 2, 3, 4, 5}; //connect to the row pinouts of the keypad
    byte colPins[COLS] = {23, 22, 21, 20, 19, 18, 17,}; //connect to the column pinouts of the keypad
  
    char keyMap[ROWS][COLS] = {
      {1,2,3,4,5,6,7},
      {8,9,10,11,12,13,14},
      {15,16,17,18,19,20,21},
      {22,23,24,25,26,27,28},
      {29,30,31,32,33,34,35},
      {36,37}
    };
  
    Keypad kpd = Keypad( makeKeymap(keyMap), rowPins, colPins, ROWS, COLS); 
    
  //Encoder Section
  PolledEncoder encoderA;
  PolledEncoder encoderB;
  PolledEncoder encoderC;
  PolledEncoder encoderD;
  
  
  void setup() 
  {
      encoderA.begin(33, 34); 
      encoderB.begin(36, 37);
      encoderC.begin(38, 39);
      encoderD.begin(40, 41);
      Joystick.X(512);  // same as Windows default
      Joystick.Y(512);  // same as Windows default
      Joystick.Zrotate(512);  // same as Windows default
      Joystick.useManualSend(true);
  }
  
  void loop() 
  {
  Joystick.hat(1, 361); //For some reason this is Hat #4
  Joystick.hat(2, 361); // This is Hat #3
  Joystick.hat(3, 361); // This is Hat #2
  Joystick.hat(4, 361); // Hat #1
    //Joystick.X(analogRead(24));
    //Joystick.Y(analogRead(25));
    //Joystick.Z(analogRead(26));
    int xValue;
    int yValue;
    int zValue;
    int deadZone = 3;
    xValue = analogRead(24);
    yValue = analogRead(25);
    zValue = analogRead(26);
    if (xValue < (15 + deadZone)) xValue = 15;
    if (xValue > (998 - deadZone)) xValue = 998;
    if (yValue < (15 + deadZone)) yValue = 15;
    if (yValue > (998 - deadZone)) yValue = 998;
    if (zValue < (15 + deadZone)) zValue = 15;
    if (zValue > (998 - deadZone)) zValue = 998;
    Joystick.X(xValue);
    Joystick.Y(yValue);
    Joystick.Zrotate(zValue);
    

    
    encoderA.tick();             
    encoderB.tick();
    encoderC.tick();             
    encoderD.tick();
    Joystick.send_now();
    
    if ( kpd.getKeys() )
    {
      for (int i=0; i<LIST_MAX; i++)
      {
        if ( kpd.key[i].stateChanged )
        {
          if ( kpd.key[i].kstate == PRESSED || kpd.key[i].kstate == HOLD)
          {
              Joystick.button(kpd.key[i].kchar, 1);
          } else if ( kpd.key[i].kstate == RELEASED )
          {
              Joystick.button(kpd.key[i].kchar, 0);
          }
          Joystick.send_now();
        }
      }
    }
    if (encoderA.valueChanged()) 
      {
        if (encoderA.getValue() == 1)
        {
          Joystick.button(38, 1);
          encoderA.setValue(0);
          Joystick.send_now();
          delay(30);
          Joystick.button(38, 0);
        }
          else if (encoderA.getValue() == -1)
            {
            Joystick.button(39, 1);
            encoderA.setValue(0);
            Joystick.send_now();
            delay(30);
            Joystick.button(39, 0);
            }
             else
             {
              encoderA.setValue(0);
              Joystick.button(38, 0);
              Joystick.button(39, 0);
              Joystick.send_now();
              }
        }
      if (encoderB.valueChanged()) // do we have a new value?
      {
        if (encoderB.getValue() == 1)
        {
          Joystick.button(40, 1);
          encoderB.setValue(0);
          Joystick.send_now();
          delay(30);
          Joystick.button(40, 0);
        }
          else if (encoderB.getValue() == -1)
            {
            Joystick.button(41, 1);
            encoderB.setValue(0);
            Joystick.send_now();
            delay(30);
            Joystick.button(41, 0);
            }
             else
             {
              encoderB.setValue(0);
              Joystick.button(40, 0);
              Joystick.button(41, 0);
              Joystick.send_now();
              }
        }
       if (encoderC.valueChanged()) // do we have a new value?
        {
          if (encoderC.getValue() == 1)
            {
              Joystick.button(42, 1);
              encoderC.setValue(0);
              Joystick.send_now();
              delay(30);
              Joystick.button(42, 0);
            }
              else if (encoderC.getValue() == -1)
                {
                Joystick.button(43, 1);
                encoderC.setValue(0);
                Joystick.send_now();
                delay(30);
                Joystick.button(43, 0);
                }
                 else
                 {
                  encoderC.setValue(0);
                  Joystick.button(43, 0);
                  Joystick.button(44, 0);
                  Joystick.send_now();
                  }
        }
           if (encoderD.valueChanged()) // do we have a new value?
          {
            if (encoderD.getValue() == 1)
            {
              Joystick.button(45, 1);
              encoderD.setValue(0);
              Joystick.send_now();
              delay(30);
              Joystick.button(45, 0);
            }
              else if (encoderD.getValue() == -1)
                {
                Joystick.button(46, 1);
                encoderD.setValue(0);
                Joystick.send_now();
                delay(30);
                Joystick.button(46, 0);
                }
                 else
                 {
                  encoderD.setValue(0);
                  Joystick.button(45, 0);
                  Joystick.button(46, 0);
                  Joystick.send_now();
                  }
        }
    delay(5);
  }
 
What is JOYSTICK_SIZE set to?

If for example: you are using the default size 12:

If you look at the descriptor (usb_desc.c)
Code:
#if JOYSTICK_SIZE == 12
static uint8_t joystick_report_desc[] = {
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x04,                     // Usage (Joystick)
        0xA1, 0x01,                     // Collection (Application)
        0x15, 0x00,                     //   Logical Minimum (0)
        0x25, 0x01,                     //   Logical Maximum (1)
        0x75, 0x01,                     //   Report Size (1)
        0x95, 0x20,                     //   Report Count (32)
        0x05, 0x09,                     //   Usage Page (Button)
        0x19, 0x01,                     //   Usage Minimum (Button #1)
        0x29, 0x20,                     //   Usage Maximum (Button #32)
        0x81, 0x02,                     //   Input (variable,absolute)
        0x15, 0x00,                     //   Logical Minimum (0)
        0x25, 0x07,                     //   Logical Maximum (7)
        0x35, 0x00,                     //   Physical Minimum (0)
        0x46, 0x3B, 0x01,               //   Physical Maximum (315)
        0x75, 0x04,                     //   Report Size (4)
        0x95, 0x01,                     //   Report Count (1)
        0x65, 0x14,                     //   Unit (20)
        0x05, 0x01,                     //   Usage Page (Generic Desktop)
        0x09, 0x39,                     //   Usage (Hat switch)
        0x81, 0x42,                     //   Input (variable,absolute,null_state)
        0x05, 0x01,                     //   Usage Page (Generic Desktop)
        0x09, 0x01,                     //   Usage (Pointer)
        0xA1, 0x00,                     //   Collection ()
  [COLOR="#FF0000"]      0x15, 0x00,                     //     Logical Minimum (0)
        0x26, 0xFF, 0x03,               //     Logical Maximum (1023)
        0x75, 0x0A,                     //     Report Size (10)
        0x95, 0x04,                     //     Report Count (4)
        0x09, 0x30,                     //     Usage (X)
        0x09, 0x31,                     //     Usage (Y)
        0x09, 0x32,                     //     Usage (Z)
        0x09, 0x35,                     //     Usage (Rz)
        0x81, 0x02,                     //     Input (variable,absolute)[/COLOR]
        0xC0,                           //   End Collection
        0x15, 0x00,                     //   Logical Minimum (0)
        0x26, 0xFF, 0x03,               //   Logical Maximum (1023)
        0x75, 0x0A,                     //   Report Size (10)
        0x95, 0x02,                     //   Report Count (2)
        0x09, 0x36,                     //   Usage (Slider)
        0x09, 0x36,                     //   Usage (Slider)
        0x81, 0x02,                     //   Input (variable,absolute)
        0xC0                            // End Collection
};
You will see that these fields are only 10 bits in size so range is 0-1023

But if you are using the SIZE==64, then those fields are 16 bits:
Code:
        0xA1, 0x00,                     // Collection ()
        0x15, 0x00,                     // Logical Minimum (0)
        0x27, 0xFF, 0xFF, 0, 0,         // Logical Maximum (65535)
        0x75, 0x10,                     // Report Size (16)
        0x95, 23,                       // Report Count (23)
        0x09, 0x30,                     // Usage (X)
        0x09, 0x31,                     // Usage (Y)
        0x09, 0x32,                     // Usage (Z)
        0x09, 0x33,                     // Usage (Rx)
        0x09, 0x34,                     // Usage (Ry)
        0x09, 0x35,                     // Usage (Rz)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x81, 0x02,                     // Input (variable,absolute)
 
Sorry I don't have time... to setup your full example and try it out, but I will try to give a few more hints or things to try out.

Code:
    xValue = analogRead(24);
    yValue = analogRead(25);
    zValue = analogRead(26);
    if (xValue < (15 + deadZone)) xValue = 15;
    if (xValue > (998 - deadZone)) xValue = 998;
    if (yValue < (15 + deadZone)) yValue = 15;
    if (yValue > (998 - deadZone)) yValue = 998;
    if (zValue < (15 + deadZone)) zValue = 15;
    if (zValue > (998 - deadZone)) zValue = 998;
By default, analogRead will return values 0-1023, as it defaults to 10 bit resolution.
https://www.pjrc.com/store/teensy41.html#analog

18 pins can be used an analog inputs, for reading sensors or other analog signals. Basic analog input is done with the analogRead function. The default resolution is 10 bits (input range 0 to 1023), but can be adjusted with analogReadResolution. The hardware allows up to 12 bits of resolution, but in practice only up to 10 bits are normally usable due to noise. More advanced use is possible with the ADC library.

If you want the full range of 0-65535, you can use something like the map function.

And then there is the issue of the lines of code like: if (xValue > (998 - deadZone)) xValue = 998;
So even if you were to get a value > 1024 this would limit it.
And likewise will limit the bottom some...

You probably should check the actual values you are seening on the analogRead
 
Thanks for the help, I pulled those lines out. With those removed I'm getting 1024 to 63800 with both reading being a bit unstable (i.e. flickering between different numbers).

Regardless, it still shouldn't be starting at anything apart from 0, correct?
 
I checked the analogue for one of the potentiometers outputs using the code below.

Got a range of about 60 to about 3939. Not sure what that means!


Code:
uint16_t CH01 = A13;
uint16_t min_value = 0xffff;
uint16_t max_value = 0;
uint16_t last_value = 0;

void setup() {
    while (!Serial) ; // wait for serial
    Serial.begin(115200);
    analogReadResolution(16);
}
void loop() {
    uint16_t new_value = analogRead(24);
    if (new_value != last_value) {
        last_value = new_value;
        if (new_value > max_value) max_value = new_value;
        if (new_value < min_value) min_value = new_value;
        Serial.printf("%d : %d %d\n", new_value, min_value, max_value);
    }
    delay(50); // not sure if delay needed or how long to go between samples...
}
 
I don't know. That is I don't know what your analogRead is reading?

For example, is it reading from some gimble with a pot? like connection? Some of them might have some form of limitation in them. That is they may not go all the way in either direction. Could be they limited it by adding resistors or the like. Or physically the device does not go through the entire range.

When I built my own remote control (many years ago), I had some calibration routines where I would move the joysticks through their complete range and record mins/maxes and for self-centering axis, where the center points were. I would then use map functions to give me my ranges. Note: I did not use USB types as an output, but instead built packets of data, I sent using XBees to my robot.
I also used averages of the last n values to smooth things out. There are more advanced code out there.

Note: I found that In some cases I did not want to use the full range of possible values. That is I found for me, best for finding the min/max's to move straight up, down and left, right and not go through the diagonals like 45 degrees, as they were giving bigger ranges, but I rather have my UP/Down to give the full range.


But one version of the code was doing:

Code:
//=============================================================================
// Calibrate and Normalize AnalogValue
//=============================================================================
void ReadCalibrateAndNormalizeAnalog(byte iRaw, byte i, uint16_t wAnalog) {
  g_awRawSums[i] -= g_aawRawAnalog[iRaw][i];        // remove the value we overwrite from sum
  g_aawRawAnalog[iRaw][i] = wAnalog;
  g_awRawSums[i] += wAnalog;        // Add the new value in
  // Lets calculate our calibrated values from 0-255 whith 128 as center point
  if (g_awRawSums[i] <= g_awAnalogMins[i])
    diyp.ab[g_aiRawToDIYP[i]] = 0;                // Take care of out of range low
  else if (g_awRawSums[i] >= g_awAnalogMaxs[i])
    diyp.ab[g_aiRawToDIYP[i]] = 255;              // out of range high
  else if (!g_afAnalogUseMids[i])
    diyp.ab[g_aiRawToDIYP[i]] = map(g_awRawSums[i], g_awAnalogMins[i], g_awAnalogMaxs[i], 0, 255);
  else if (g_awRawSums[i] <= g_awAnalogMids[i])
    diyp.ab[g_aiRawToDIYP[i]] = map(g_awRawSums[i], g_awAnalogMins[i], g_awAnalogMids[i], 0, 128);
  else
    diyp.ab[g_aiRawToDIYP[i]] = map(g_awRawSums[i], g_awAnalogMids[i], g_awAnalogMaxs[i], 128, 255);

  // If in normal mode make sure we transmit as often as possible...
  if (g_bMode==MODE_NORMAL)
    CheckAndTransmitDataPacket(&diyp);
}

Again where I kept a sum of the last N readings, and then took care of values that were outside the valid ranges, and then took care of the deadzone, and then I mapped
the values less than deadzone and the values greater than deadzone separate, as to hopefully normalize the values from 0-255 with 128 at center

But I don't know if any of this applies to your case.
 
That looks pretty dang complicated and quite a bit over my head. If the code I quoted above isn't what I should be using to see what my analogRead is then what should I be doing?

It seems like I should be able to scale whatever my values are with map(), but I haven't been able to get that to work yet.
 
It's not very pretty or efficient, but this seems to have resolved the issue.

the bit I'm using:
Code:
        long xValue;
        long yValue;
        long zValue;
        xValue = (map(analogRead(24),16,996,0,1024))*64;
        yValue = (map(analogRead(25),16,996,0,1024))*64;
        zValue = (map(analogRead(26),16,996,0,1024))*64;
        Joystick.X(xValue);
        Joystick.Y(yValue);
        Joystick.Zrotate(zValue);




In context with the full code:
Code:
        //Six rows on pins 0, 1, 2, 3, 4, 5
        //Seven columns on pins 23, 22, 21, 20, 19, 18, 17,
        //Four rotary encoders, pins (33,34) (36,37) (38,39) (40,41)
        //Three potentiometers, pins 24, 25, 26
        
        
          #include <Keypad.h>
          #define JOYSTICK_SIZE 64 // 12 = normal, 64 = extreme joystick
          #include "EncoderTool.h"
          using namespace EncoderTool;
        
        //Button Matrix Section
          const byte ROWS = 6; //four rows
          const byte COLS = 7; //four columns
        
          byte rowPins[ROWS] = {0, 1, 2, 3, 4, 5}; //connect to the row pinouts of the keypad
          byte colPins[COLS] = {23, 22, 21, 20, 19, 18, 17,}; //connect to the column pinouts of the keypad
        
          char keyMap[ROWS][COLS] = {
            {1,2,3,4,5,6,7},
            {8,9,10,11,12,13,14},
            {15,16,17,18,19,20,21},
            {22,23,24,25,26,27,28},
            {29,30,31,32,33,34,35},
            {36,37}
          };
        
          Keypad kpd = Keypad( makeKeymap(keyMap), rowPins, colPins, ROWS, COLS); 
          
        //Encoder Section
        PolledEncoder encoderA;
        PolledEncoder encoderB;
        PolledEncoder encoderC;
        PolledEncoder encoderD;
        
        
        void setup() 
        {
            encoderA.begin(33, 34); 
            encoderB.begin(36, 37);
            encoderC.begin(38, 39);
            encoderD.begin(40, 41);
            Joystick.X(512);  // same as Windows default
            Joystick.Y(512);  // same as Windows default
            Joystick.Zrotate(512);  // same as Windows default
            analogReadAveraging(50);
            Joystick.useManualSend(true);
        }
        
        void loop() 
        {
        Joystick.hat(1, 361); //For some reason this is Hat #4
        Joystick.hat(2, 361); // This is Hat #3
        Joystick.hat(3, 361); // This is Hat #2
        Joystick.hat(4, 361); // Hat #1
      
        long xValue;
        long yValue;
        long zValue;
        xValue = (map(analogRead(24),16,996,0,1024))*64;
        yValue = (map(analogRead(25),16,996,0,1024))*64;
        zValue = (map(analogRead(26),16,996,0,1024))*64;
        Joystick.X(xValue);
        Joystick.Y(yValue);
        Joystick.Zrotate(zValue);
      
        encoderA.tick();             
        encoderB.tick();
        encoderC.tick();             
        encoderD.tick();
        Joystick.send_now();
          
          if ( kpd.getKeys() )
          {
            for (int i=0; i<LIST_MAX; i++)
            {
              if ( kpd.key[i].stateChanged )
              {
                if ( kpd.key[i].kstate == PRESSED || kpd.key[i].kstate == HOLD)
                {
                    Joystick.button(kpd.key[i].kchar, 1);
                } else if ( kpd.key[i].kstate == RELEASED )
                {
                    Joystick.button(kpd.key[i].kchar, 0);
                }
                Joystick.send_now();
              }
            }
          }
          if (encoderA.valueChanged()) 
            {
              if (encoderA.getValue() == 1)
              {
                Joystick.button(38, 1);
                encoderA.setValue(0);
                Joystick.send_now();
                delay(30);
                Joystick.button(38, 0);
              }
                else if (encoderA.getValue() == -1)
                  {
                  Joystick.button(39, 1);
                  encoderA.setValue(0);
                  Joystick.send_now();
                  delay(30);
                  Joystick.button(39, 0);
                  }
                   else
                   {
                    encoderA.setValue(0);
                    Joystick.button(38, 0);
                    Joystick.button(39, 0);
                    Joystick.send_now();
                    }
              }
            if (encoderB.valueChanged()) // do we have a new value?
            {
              if (encoderB.getValue() == 1)
              {
                Joystick.button(40, 1);
                encoderB.setValue(0);
                Joystick.send_now();
                delay(30);
                Joystick.button(40, 0);
              }
                else if (encoderB.getValue() == -1)
                  {
                  Joystick.button(41, 1);
                  encoderB.setValue(0);
                  Joystick.send_now();
                  delay(30);
                  Joystick.button(41, 0);
                  }
                   else
                   {
                    encoderB.setValue(0);
                    Joystick.button(40, 0);
                    Joystick.button(41, 0);
                    Joystick.send_now();
                    }
              }
             if (encoderC.valueChanged()) // do we have a new value?
              {
                if (encoderC.getValue() == 1)
                  {
                    Joystick.button(42, 1);
                    encoderC.setValue(0);
                    Joystick.send_now();
                    delay(30);
                    Joystick.button(42, 0);
                  }
                    else if (encoderC.getValue() == -1)
                      {
                      Joystick.button(43, 1);
                      encoderC.setValue(0);
                      Joystick.send_now();
                      delay(30);
                      Joystick.button(43, 0);
                      }
                       else
                       {
                        encoderC.setValue(0);
                        Joystick.button(43, 0);
                        Joystick.button(44, 0);
                        Joystick.send_now();
                        }
              }
                 if (encoderD.valueChanged()) // do we have a new value?
                {
                  if (encoderD.getValue() == 1)
                  {
                    Joystick.button(45, 1);
                    encoderD.setValue(0);
                    Joystick.send_now();
                    delay(30);
                    Joystick.button(45, 0);
                  }
                    else if (encoderD.getValue() == -1)
                      {
                      Joystick.button(46, 1);
                      encoderD.setValue(0);
                      Joystick.send_now();
                      delay(30);
                      Joystick.button(46, 0);
                      }
                       else
                       {
                        encoderD.setValue(0);
                        Joystick.button(45, 0);
                        Joystick.button(46, 0);
                        Joystick.send_now();
                        }
              }
          delay(5);
        }
 
Sounds good, glad you are getting it working. but wondering if you could simply do:
Code:
        xValue = map(analogRead(24),16,996,0,65535);

As you mentioned, my earlier code is a little more complex.

Some of the Reasons for the complexity is:

Suppose I do: my min and max are set to 16 and 996.
and suppose I do an analog read it this time returned 14
As your code is currently and assuming it is using signed math. This would probably set your
XValue to something like: -133 more or less, which would be bad enough,
but the joystick.X(xValue) actually takes an unsigned int. So it may turn into a very large number.

Likewise if you get a value like 998, again the map function will map this to a value > 65536... Which either will fail the joystick.X() function or give you
bogus value.

So the code fragment logically something like:
Code:
        uint16_t analog_value = analogRead(24);
        if (analog_value < 16) xValue = 0;
        else if (analog_value > 996) xValue = 65535;
        else xValue = map(analog_value),16,996,0,65535);
Which worked great for things like sliders and the like. But if your case has a self centering axis, where you may want a dead zone, such that thing don't creep
You can update the above code. Now lets assume that the center point is 512 and you want a 3 units in both direction fudge factor.
The above code could be modified like:
Code:
        uint16_t analog_value = analogRead(24);
        if (analog_value < 16) xValue = 0;
        else if (analog_value > 996) xValue = 65535;
        else if ((abs(analog_value) - 512) < 3) xValue = 32767;  // center location.
        else xValue = map(analog_value),16,996,0,65535);
Now of course you can unwind the abs function and do it with two checks in the if

Hope that makes sense.
The above code goes a few steps farther, in that I tried to avoid the magic numbers 16(min), 996(max), 512(center) and put them into variables. Also the variables were setup
for multiple different axis, sliders... So these type values were put into arrays of values....

But if I were you, I would leave it simple to start, maybe then add in bounds checking... And see what you need or don't need.
Hope this makes sense.
 
Back
Top