Understanding Joystick Programming

This is my code, created by merging several example codes, however I don't understand the pin assignment on the Joystick example code. This is for the Arduino Pro Micro, here is a pinout diagram. The Pin mode section still uses the old pin aliases, I will change that.

#include <Joystick.h>
#include <Keyboard.h>
#include <Encoder.h>

//button press assignment
int button0 = 0;   
int button1 = 1;   
int button2 = 2;   
int button3 = 3;   
int button4 = 4;  
int button5 = 5;
int button6 = 6;
int button0 = int rotarypushl;
int button1 = int rotarypushr;
int button2 = int rotarypushm;
int button3 = int upshift;
int button4 = int downshift;
int button5 = int clutch;
int button6 = int flick0;
int button7 = int flick1;
//encoder turns assignment
int kll = 8;
int klr = 9;   
int krl = 10;   
int krr = 14;       
int kml = 15;
int kmr = 16;
Encoder knobLeft(8, 9);
Encoder knobRight(10, 14);
Encoder knobMiddle(15, 16);
//joystick assignment    
int pin18 = 18;  
int pin19 = 19;
int pin20 = 20;
int pin21 = 21;
#define JOYSTICK_COUNT 2
Joystick_ Joystick[JOYSTICK_COUNT] = 
{
  Joystick_(0x03, JOYSTICK_TYPE_JOYSTICK,  4, 2,  true, true, false, false, false, false, false, false, false, false, false),
  Joystick_(0x04, JOYSTICK_TYPE_JOYSTICK,  8, 1,  true, true,  true,  true, false, false, false, false, false, false, false),
};
const bool testAutoSendMode = true;

void setup() 
{
keyboard.begin();

pinMode(pin1, INPUT_PULLUP);                        
pinMode(pin2, INPUT_PULLUP);                        
pinMode(pin3, INPUT_PULLUP);
pinMode(pin4, INPUT_PULLUP);
pinMode(pin5, INPUT_PULLUP);
pinMode(pin6, INPUT_PULLUP);
pinMode(pin7, INPUT_PULLUP);
pinMode(pin8, INPUT_PULLUP);
pinMode(pin9, INPUT_PULLUP);
pinMode(pin10, INPUT_PULLUP);
pinMode(pin14, INPUT_PULLUP);
pinMode(pin15, INPUT_PULLUP);
pinMode(pin16, INPUT_PULLUP);
pinMode(pin18, INPUT_PULLUP);
pinMode(pin19, INPUT_PULLUP);
pinMode(pin20, INPUT_PULLUP);
pinMode(pin21, INPUT_PULLUP);

for (int index = 0; index < JOYSTICK_COUNT; index++)
  {
    Joystick[index].setXAxisRange(-127, 127);
    Joystick[index].setYAxisRange(-127, 127);
  
    if (testAutoSendMode)
    {
      Joystick[index].begin();
    }
    else
    {
      Joystick[index].begin(false);
    }
  }

long positionLeft  = -999;
long positionRight = -999;
long positionMiddle = -999;

void loop() {
  long newLeft, newRight, newMiddle;
  newLeft = knobLeft.read();
  newMiddle = knobMiddle.read();
  newRight = knobRight.read();
  if (newLeft != positionLeft || newRight != positionRight || newMiddle != positionMiddle) 
  {
    Serial.print("Left = ");
    Serial.print(newLeft);
    Serial.print(", Right = ");
    Serial.print(newRight);
    Serial.println();
    positionLeft = newLeft;
    positionRight = newRight;
    positionMiddle = newMiddle;
  }
  // if a character is sent from the serial monitor,
  // reset both back to zero.
  if (Serial.available()) {
    Serial.read();
    Serial.println("Reset both knobs to zero");
    knobLeft.write(0);
    knobRight.write(0);
    knobMiddle.write(0);
  }
  if (digitalRead(A0) != 0)
  {
    digitalWrite(LED_BUILTIN, 0);
    return;
  }
  
  if (millis() >= gNextTime)
  {
    if (gCurrentStep < 17)
    {
      gNextTime = millis() + gcButtonDelta;
      testSingleButtonPush(gJoystickId, gCurrentStep);
    } 
    else if (gCurrentStep < (17 + 4))
    {
      gNextTime = millis() + gcButtonDelta;
      testMultiButtonPush(gJoystickId, gCurrentStep - 17);
    }
    else if (gCurrentStep < (17 + 4 + 1024 + 128))
    {
      gNextTime = millis() + gcAnalogDelta;
      testXYAxis(gJoystickId, gCurrentStep - (17 + 4));
    }
    
    if (testAutoSendMode == false)
    {
      Joystick[gJoystickId].sendState();
    }
    
    gCurrentStep++;
    if (gCurrentStep == (17 + 4 + 1024 + 128))
    {
      gNextTime = millis() + gcCycleDelta;
      gCurrentStep = 0;
      
      if (++gJoystickId >= JOYSTICK_COUNT)
      {
        gJoystickId = 0;
      }
    }
  }
}



Here is the example code I copied from.

// Program used to test using the Arduino Joystick Library 
// to create multiple Joystick objects on a single Arduino 
// Leonardo or Arduino Micro.
//
// Each joystick has a unique configuration.
//
// Matthew Heironimus
// 2016-05-13 - Original Version
// 2022-03-29 - Updated to work with Joystick Library v2.1.0
 
#include <Joystick.h>

#define JOYSTICK_COUNT 4

Joystick_ Joystick[JOYSTICK_COUNT] = {
  Joystick_(0x03, JOYSTICK_TYPE_JOYSTICK,  4, 2,  true, true, false, false, false, false, false, false, false, false, false),
  Joystick_(0x04, JOYSTICK_TYPE_JOYSTICK,  8, 1,  true, true,  true,  true, false, false, false, false, false, false, false),
  Joystick_(0x05, JOYSTICK_TYPE_JOYSTICK, 16, 0, false, true, false,  true, false, false,  true,  true, false, false, false),
  Joystick_(0x06, JOYSTICK_TYPE_JOYSTICK, 32, 1,  true, true, false,  true,  true, false, false, false, false, false, false)
};

// Set to true to test "Auto Send" mode or false to test "Manual Send" mode.
//const bool testAutoSendMode = true;
const bool testAutoSendMode = false;

const unsigned long gcCycleDelta = 1000;
const unsigned long gcAnalogDelta = 25;
const unsigned long gcButtonDelta = 500;
unsigned long gNextTime = 0;
unsigned int gCurrentStep = 0;
int gJoystickId = 0;

void testSingleButtonPush(int joystickId, unsigned int button)
{
  if (button > 0)
  {
    Joystick[joystickId].releaseButton(button - 1);
  }
  if (button < 16)
  {
    Joystick[joystickId].pressButton(button);
  }
}

void testMultiButtonPush(int joystickId, unsigned int currentStep) 
{
  for (int button = 0; button < 16; button++)
  {
    if ((currentStep == 0) || (currentStep == 2))
    {
      if ((button % 2) == 0)
      {
        Joystick[joystickId].pressButton(button);
      } else if (currentStep != 2)
      {
        Joystick[joystickId].releaseButton(button);
      }
    } // if ((currentStep == 0) || (currentStep == 2))
    if ((currentStep == 1) || (currentStep == 2))
    {
      if ((button % 2) != 0)
      {
        Joystick[joystickId].pressButton(button);
      } else if (currentStep != 2)
      {
        Joystick[joystickId].releaseButton(button);
      }
    } // if ((currentStep == 1) || (currentStep == 2))
    if (currentStep == 3)
    {
      Joystick[joystickId].releaseButton(button);
    } // if (currentStep == 3)
  } // for (int button = 0; button < 32; button++)
}

void testXYAxis(int joystickId, unsigned int currentStep)
{
  int x;
  int y;

  if (currentStep < 255)
  {
    x = currentStep - 127;
    y = -127;
  } 
  else if (currentStep < 510)
  {
    x = 127;
    y = currentStep - 255 - 127;
  }
  else if (currentStep < 765)
  {
    x = 127 - (currentStep - 510);
    y = 127;
  }
  else if (currentStep < 1020)
  {
    x = -127;
    y = 127 - (currentStep - 765);
  }
  else if (currentStep <= 1020 + 127)
  {
    x = currentStep - 1020 - 127;
    y = currentStep - 1020 - 127;
  }

  Joystick[joystickId].setXAxis(x);
  Joystick[joystickId].setYAxis(y);
}

void setup() {

  for (int index = 0; index < JOYSTICK_COUNT; index++)
  {
    Joystick[index].setXAxisRange(-127, 127);
    Joystick[index].setYAxisRange(-127, 127);
  
    if (testAutoSendMode)
    {
      Joystick[index].begin();
    }
    else
    {
      Joystick[index].begin(false);
    }
  }
  
  pinMode(A0, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {

  // System Disabled
  if (digitalRead(A0) != 0)
  {
    digitalWrite(LED_BUILTIN, 0);
    return;
  }

  // Turn indicator light on.
  digitalWrite(LED_BUILTIN, 1);
  
  if (millis() >= gNextTime)
  {
   
    if (gCurrentStep < 17)
    {
      gNextTime = millis() + gcButtonDelta;
      testSingleButtonPush(gJoystickId, gCurrentStep);
    } 
    else if (gCurrentStep < (17 + 4))
    {
      gNextTime = millis() + gcButtonDelta;
      testMultiButtonPush(gJoystickId, gCurrentStep - 17);
    }
    else if (gCurrentStep < (17 + 4 + 1024 + 128))
    {
      gNextTime = millis() + gcAnalogDelta;
      testXYAxis(gJoystickId, gCurrentStep - (17 + 4));
    }
    
    if (testAutoSendMode == false)
    {
      Joystick[gJoystickId].sendState();
    }
    
    gCurrentStep++;
    if (gCurrentStep == (17 + 4 + 1024 + 128))
    {
      gNextTime = millis() + gcCycleDelta;
      gCurrentStep = 0;
      
      if (++gJoystickId >= JOYSTICK_COUNT)
      {
        gJoystickId = 0;
      }
    }
  }
}

The components I am using are 3 rotary encoder, with push buttons connected for all 3, 2 joysticks, no push switches connected, 2 "flick" switches and 3 micro lever switches, configured to be normally open.

what do you call the pin assignment?

If so, here is the definition of each parameter:

	uint8_t hidReportId, 	uint8_t joystickType,    uint8_t buttonCount, 	uint8_t hatSwitchCount, 
              (0x03,       JOYSTICK_TYPE_JOYSTICK,                4,                    2, 

bool includeXAxis, bool includeYAxis, bool includeZAxis, bool includeRxAxis, bool includeRyAxis, bool includeRzAxis, bool includeRudder, bool includeThrottle, bool includeAccelerator ,bool includeBrake, bool includeSteering)
 true,                    true,                  false,             false,            false,              false,                 false,         false,                false,                 false,                false

Yes, I understand that when I connect a joystick to an Arduino, I have 2 pins for reading data for each axis, but in the programming there is only one pin for each joystick.

Why is there only one hidReportID when a joystick sends 2 inputs?

See if this link helps.

To which part of the code are you referring to?

This part especially the 0x0(number) part

that's a descriptor ID for the HID report pertaining to that instance. So it's any number (besides 1 and 2 if I remember correctly) as long as it's unique for your application.

EDIT: confirm you should not use 1 and 2 as per the GitHub comment

  • uint8_t hidReportId - Default: 0x03 - Indicates the joystick's HID report ID. This value must be unique if you are creating multiple instances of Joystick. Do not use 0x01 or 0x02 as they are used by the built-in Arduino Keyboard and Mouse libraries.

You understand that all the buttons' states from this joystick will be packed into one USB transaction describing the state.

So how does the arduino know which pin I have wired the joystick to?

that's you code, when you do things like

or

you are filling in the attributes describing the state of the joystick that will be part of the HID report over USB. The instance of the Joystick_ class does not have any clue about what's the real physical device providing input. it's just the bridge to report stuff over HID

Thank you

the HID report is then sent either immediately after you modify one attribute or at a later stage when you call sendState() depending on the bool (true/no parameter or false) you passed to the begin method here

if (testAutoSendMode) {
  Joystick[index].begin();
} else {
  Joystick[index].begin(false);
}

not sending immediately lets you for example aggregate the new state of multiple buttons in one report.

In the joystick library, to configure the joystick options, you do this:

Joystick_ leftJoystick(0x01, JOYSTICK_TYPE_JOYSTICK, 2, 0, false, false, false, false, false, false, false, false, false, false, false);
Joystick_ rightJoystick(0x02, JOYSTICK_TYPE_JOYSTICK, 2, 0, false, false, false, false, false, false, false, false, false, false, false);

For each joystick there is only one pin ID despite each joystick using 2 analogue pins, how do I specify both pins, and if not, how do I know where to wire each input pin to?

Post a link to the library.

You should see an explanation in the source, and also examples in a folder within the library files.

Did you find your two lines in any example?

Yes,but I still don't understand how to wire the joystick?

I checked the default test configuration and the multiple joystick configuration.

Do the examples work with your joystick?