Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 8 of 8

Thread: Issue with joystick axis scaling on Linux

  1. #1
    Junior Member
    Join Date
    Jan 2018
    SW England

    Issue with joystick axis scaling on Linux

    Hi guys.

    My first post here, so I guess the drinks are on me I've just started exploring the delightful world of Teensy having had some experience with Arduino and also PIC devices (although not for a few years), and very impressed and inspired so far.

    As a first experiment I've put together a little box with a Teensy LC, a few pots (50k linear), switches and a rotary encoder which I have initially set up to function as an extra joystick controller (for flight simulator).

    However, I've hit upon an issue with the scaling of the analog axis when connected to Linux. I've seached through the forums here and haven't seen mention of anything similar, but apologies if this is a known issue.

    I've used simple code to translate the pots:
    When connected to my Windows PC this functions as expected and the pot gives control over it's full range. However, when plugged into a Linux machine the joystick axis only operates across a reduced range of the pot (approx 9 o'clock to 3 o'clock). In other words the axis value, as seen in the joystick control panel, is at a minimum when pot is at 9oc and maximum at 3oc. This greatly reduces the usablity.

    I can fix this by scaling the pot in code:
     Joystick.X(map(analogRead(potPIN[0]), 0, 1023, 60, 960));
    Which then gives me full range back, but then will no longer work correctly when connected to Wndows.

    I've checked this on both my Linux desktop (running KDE Plasma on Arch with Linux kernel 4.14.14) and my laptop (running Manjaro KDE (which is Arch based) and again Linux 4.14.14) and see the same on both. Teensy coding was done on Arduino 1.8.5 / Teensduino 1.41.

    Have I missed something? Or is there perhaps workaround?


  2. #2
    Junior Member
    Join Date
    Jan 2018
    SW England
    Here's the full code for reference:
    #include <Encoder.h>
    #include <Bounce2.h>
    // Teensy LC Pin definitions
    int ledPIN[7] = {10, 9, 22, 23, 20, 26, 13};
    int buttonPIN[8] = {2,3,4,5,6,7,8,11};
    int togglePIN = 0;
    int pushPIN = 1;
    int potPIN[6] = {14,15,16,17,18,19};
    int encPIN1 = 12;
    int encPIN2 = 25;
    int encswPIN = 24;
    int pedalPIN = 21;
    // Array to store pot values
    int potVal[6];
    // Array to store button values
    bool butt[16];
    Bounce butt_0 = Bounce(buttonPIN[0], 5);
    Bounce butt_1 = Bounce(buttonPIN[1], 5);
    Bounce butt_2 = Bounce(buttonPIN[2], 5);
    Bounce butt_3 = Bounce(buttonPIN[3], 5);
    Bounce butt_4 = Bounce(buttonPIN[4], 5);
    Bounce butt_5 = Bounce(buttonPIN[5], 5);
    Bounce butt_6 = Bounce(buttonPIN[6], 5);
    Bounce butt_7 = Bounce(buttonPIN[7], 5);
    Bounce toggleSW = Bounce(togglePIN, 5);
    Bounce pushSW = Bounce(pushPIN, 5);
    Bounce encPush = Bounce(encswPIN, 5);
    Encoder rotary(encPIN1, encPIN2);
    long encoderPosition = -999;
    long newPosition;
    long oldPosition;
    IntervalTimer usbTimer;
    IntervalTimer encoderTimer;
    // ############## SETUP ################ //
    void setup() {
    int i;
    // Configure Pins
    for (i=0; i < 7 ; i++) { 
      pinMode(ledPIN[i], OUTPUT);
    for (i=0; i < 8; i++) {
      pinMode(buttonPIN[i], INPUT_PULLUP);  
    for (i=0; i < 6; i++) {
      pinMode(potPIN[i], INPUT);
    pinMode(pushPIN, INPUT_PULLUP);
    pinMode(togglePIN, INPUT_PULLUP);
    pinMode(encswPIN, INPUT_PULLUP);
    pinMode(encPIN1, INPUT_PULLUP);
    pinMode(encPIN2, INPUT_PULLUP);
    pinMode(pedalPIN, INPUT);
    //Configure joystick values to be sent only on demand
    // Configure USB Serial Baud Rate
    //Configure resolution of PWM duty cycle 
    usbTimer.begin(updateRoutine, 50000);
    encoderTimer.begin(encoderRoutine, 5000);
    butt[11] = 1;
    butt[12] = 1;
    butt[13] = 1;
    // ########## ENCODER ROUTINE ################ //
    void encoderRoutine() {
        if (newPosition != encoderPosition) {
          if (newPosition > encoderPosition) {
              butt[14] = 0;
          if (newPosition < encoderPosition) {
              butt[15] = 0;
        encoderPosition = newPosition;
    // ########## UPDATE ROUTINE ############### //
    void updateRoutine() {
    int i=0;
    // Read pot values
      Joystick.X(map(analogRead(potPIN[0]), 0, 1023, 60, 960));
      Joystick.Y(map(analogRead(potPIN[1]), 0, 1023, 60, 960));
      Joystick.Z(map(analogRead(potPIN[2]), 0, 1023, 60, 960));
      Joystick.Zrotate(map(analogRead(potPIN[3]), 0, 1023, 60, 960));
      Joystick.sliderLeft(map(analogRead(potPIN[4]), 0, 1023, 60, 960));
      Joystick.sliderRight(map(analogRead(potPIN[5]), 0, 1023, 60, 960));
    // Set LEDs
    if (butt[0] & butt[1]) {
      digitalWrite(ledPIN[0], LOW);  
      digitalWrite(ledPIN[0], HIGH);  
    if (butt[2] & butt[3]) {
      digitalWrite(ledPIN[1], LOW);  
      digitalWrite(ledPIN[1], HIGH);  
    if (butt[4] & butt[5]) {
      digitalWrite(ledPIN[2], LOW);  
      digitalWrite(ledPIN[2], HIGH);  
    if (butt[6] & butt[7]) {
      digitalWrite(ledPIN[3], LOW);  
      digitalWrite(ledPIN[3], HIGH);  
    digitalWrite(ledPIN[5], !butt[8]);
    digitalWrite(ledPIN[4], !butt[14]);
    digitalWrite(ledPIN[6], !butt[15]);
    //set joystick button if pushed
    for (i=0; i <16; i++) {
      Joystick.button(i+1, !butt[i]);
    //Send joystick values to USB  
    // reset encoder push button
    butt[14] = 1;
    butt[15] = 1;
    // stop unused buttons from sending jostick presses
    butt[11] = 1;  
    butt[12] = 1;
    butt[13] = 1;
    // ############## MAIN ################# //
    void loop() {
    //check switches
    butt[0] = butt[0] ^ butt_0.update();
    butt[1] = butt[1] ^ butt_1.update();
    butt[2] = butt[2] ^ butt_2.update();
    butt[3] = butt[3] ^ butt_3.update();
    butt[4] = butt[4] ^ butt_4.update();
    butt[5] = butt[5] ^ butt_5.update();
    butt[6] = butt[6] ^ butt_6.update();
    butt[7] = butt[7] ^ butt_7.update();
    butt[8] = butt[8] ^ toggleSW.update();
    butt[9] = butt[9] ^ pushSW.update();
    butt[10] = butt[10] ^ encPush.update();
    //check rotary encoder
    newPosition =;

  3. #3
    Do you have jstest-gtk. If so can you calibrate it to work properly. I always caiibrate manually. The save configuration is broken in jstest-gtk, but in my notes, I have the command to save the config once its calibrated in jstest. I had to make a giant dead zone for a joystick ztwist axis so i figured it out. If you can get it adjusted in jstest, I will find instructions for saving the calibration in my notes

  4. #4
    Junior Member
    Join Date
    Jan 2018
    SW England
    Thanks for the response Jeff.

    I've installed jstest-gtk and that shows the default calibration as being set to having RangeMin = 63 and RangeMax = 959 (which correlates to the scaling I manually set in the Teensy code to get it working across the fuil range).
    If I manually set these to 0 / 1023 then all is good except, as you suggest, the settings need to be redone after replugging the joystick (and presumably after rebooting).

    Why is doesn't default to 0/1023 is a mystery?

    There is a section in the Arch Linux wiki ( which covers this and suggests using a udev rule to load the calibration settings, but my initial attempts with this have failed so far (nothing in Arch is ever straighforward). I'll have another try at sorting this when i can get back to it, but if you do have another way of loading the settings that would be very appreciated.

    Thanks again.

  5. #5
    let me check my notes and I'll get back to you on how to save calibration....glad that worked

  6. #6
    sudo jscal-store /dev/input/js1
    in Ubuntu.... and of course get js(x) right....screenshot shows mistake
    not sure how arch does super user privileges, so sudo may not be the command in arch but the rest is the command to save joystick calibration
    Attached Thumbnails Attached Thumbnails Click image for larger version. 

Name:	Screenshot from 2017-12-15 23-21-50.jpg 
Views:	54 
Size:	168.1 KB 
ID:	12708  
    Last edited by Jeff Pearson; 01-22-2018 at 11:43 PM. Reason: wrong screenshot

  7. #7
    I just tried this on my Linux Mint Mate 18.2 machine, and I had to install "joystick" package in addition to jstest-gtk on that machine to get the jscal-store command to work. It's not the first time Mint has made me fix something that worked out of the box in Ubuntu proper. Hope this helps anybody using Linux developing a joystick.

  8. #8
    Junior Member
    Join Date
    Jan 2018
    SW England
    sudo jscal-store /dev/input/js1
    Thanks for that.
    While that does store the calibration, it doesn't get restored automatically on plugging the device in (like I said, nothing is straightforward in Arch ).
    I can restore the settings by manually doing:
    sudo jscal-restore /dev/input/js1
    Which i'm happy to do, certainly for the time being until I get the udev rule to work (I'm thinking that may be failing because there already exists a rule for the device as required by Teensyduino).

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts