HID Joystick - For use with X-Plane or other programs that can read HID JoySticks

Dependencies:   USBDevice mbed-rtos mbed

Fork of JoyStick by Ries Twisk

This is a simple Joystick HID that I use for xplane and a home build yoke + paddels, see this forum with the look and feel of it : http://forums.x-plane.org/index.php?showtopic=70041

The analog input are filtered with a LowPass IIR filter and the digital input's will be derived from the analog input and de-bounced.

The analog values are read at a 1Khz interval and to ensure we don't push the USB stack to much at a maximum rate of 20 updates/sec HID data is send over USB only if any values where changed. The JoyStick will send 16Bit analog values as opposite of 8 bit values that are normally used to increase accuracy of the whole system. This is well noticeable within x-plane!

The JoyStick uses the JoyStick copied from Wim Huiskamp and modified to suite my needs and the MBED RTOS libraries for reading analog inputs, sending debug data over USB and sending HID data, 3 threads in total.

main.cpp

Committer:
rvt
Date:
2016-06-22
Revision:
5:a0bb17c379ce
Parent:
4:2cc58c173de8

File content as of revision 5:a0bb17c379ce:

#include "mbed.h"
#include "USBHID.h"
#include "USBJoystick.h"
#include "LowPassFilter.h"
#include "AnalogInFiltered.h"
#include "AutoScale.h"
#include "Button.h"
#include "rtos.h"

// When set, it will send debug data over USB serial
#define TTY_DEBUG false

// Value that defines when to start sending data this prevents the noise sending loads's of data over HID
#define DATA_CHANGE_TRIGGER 8

// Activity LED for HID data
#define HIDACTIVITYLED LED3

// Structure that hold's the dataset of the input's
Mutex analogValueMutex;
struct AnalogData {
    union {
        struct {
            uint32_t bit0:1;
            uint32_t bit1:1;
            uint32_t bit2:1;
            uint32_t bit3:1;
            uint32_t bit4:1;
            uint32_t bit5:1;
            uint32_t bit6:1;
            uint32_t bit7:1;
            uint32_t bit8:1;
            uint32_t bit9:1;
            uint32_t bit10:1;
            uint32_t bit11:1;
            uint32_t bit12:1;
            uint32_t bit13:1;
            uint32_t bit14:1;
            uint32_t bit15:1;
            uint32_t bit16:1;
            uint32_t bit17:1;
            uint32_t bit18:1;
            uint32_t bit19:1;
            uint32_t bit20:1;
            uint32_t bit21:1;
            uint32_t bit22:1;
            uint32_t bit23:1;
            uint32_t bit24:1;
            uint32_t bit25:1;
            uint32_t bit26:1;
            uint32_t bit27:1;
            uint32_t bit28:1;
            uint32_t bit29:1;
            uint32_t bit30:1;
            uint32_t bit31:1;
        };
        uint32_t button;
    } buttons;
    long value1;
    long value2;
    long value3;
    long value4;
} analogData;




/**
Debug thread to show some values from the system over USB serial.
Ensure that TTY_DEBUG is defined so that these routines will get activated.
This is what I do to view the values on OSX within a terminal
$ cd /dev
$ ls | grep usbmodem
cu.usbmodemfa1232
tty.usbmodemfa1232
$ screen tty.usbmodemfa1232
*/
const char *byte_to_binary16(int x)
{
    static char b[17];
    b[0] = '\0';

    int z;
    for (z = 32768; z > 0; z >>= 1) {
        strcat(b, ((x & z) == z) ? "1" : "0");
    }

    return b;
}


void debug_thread(void const *args)
{
    // Serial port for debug data
    Serial pc(USBTX, USBRX); // tx, rx

    // Make a local copy
    AnalogData previous;
    while (true) {
        // Lock and copy input values
        analogValueMutex.lock();
        const AnalogData localCopy = analogData;
        analogValueMutex.unlock();

        // Send to USB
        pc.printf("\x1B[0;0H");
        pc.printf("Yoke and Pedals!\n\r");
        pc.printf("Analog in p20: %d  diff: %d    \n\r",localCopy.value1,localCopy.value1-previous.value1);
        pc.printf("Analog in p19: %d  diff: %d    \n\r",localCopy.value2,localCopy.value2-previous.value2);
        pc.printf("Buttons: %d    \n\r",localCopy.buttons.button);

        // Make local copy so we can show diff version
        previous = localCopy;
        Thread::wait(1000);
    }
}



void hid_thread(void const *args)
{
    //USB HID JoyStick
    USBJoystick joystick;

    // Activity led for HID data transmissions
    DigitalOut hIDActivity(HIDACTIVITYLED);

    while (true) {
        // Wait for analog in to have some data
        hIDActivity=false;
        Thread::signal_wait(0x1);
        hIDActivity=true;

        // Make a local copy of the data
        analogValueMutex.lock();
        const AnalogData localCopy = analogData;
        analogValueMutex.unlock();

        // Update joystick's info
        joystick.update(
            localCopy.value1,
            localCopy.value2,
            localCopy.value3,
            localCopy.value4,
            localCopy.buttons.button);

        // Wait 50 ms to send a other USB update
        Thread::wait(50);
    }
}



int main()
{
    analogData.buttons.button = 0;
    analogData.value1=0.;
    analogData.value2=0.;
    analogData.value3=0.;
    analogData.value4=0.;

    Button but5(p5, true, true);
    Button but6(p6, true, true);
    Button but7(p7, true, true);
    Button but8(p8, true, true);
    Button but9(p9, true, true);
    Button but10(p10, true, true);
    Button but11(p11, true, true);
    Button but12(p12, true, true);
    Button but13(p13, true, true);
    Button but14(p14, true, true);
    Button but15(p15, true, true);
    Button but16(p16, true, true);
    Button but17(p17, true, true);
    Button but21(p21, true, true);
    Button but22(p22, true, true);
    Button but23(p23, true, true);
    Button but24(p24, true, true);
    Button but25(p25, true, true);

    if (TTY_DEBUG) {
        Thread _debugThread(debug_thread);
    }

    // Initialise moving average filters
    LowPassFilter lowPassFilter1(new AnalogFilterInterface(),0.99f);   // The close the alpha value is to 1, the lower the cut-off frequency
    LowPassFilter lowPassFilter2(new AnalogFilterInterface(),0.99f);
    LowPassFilter lowPassFilter3(new AnalogFilterInterface(),0.96f);

    AutoScale autoScale1(&lowPassFilter1, -32768, 32767, 1.01);
    AutoScale autoScale2(&lowPassFilter2, -32768, 32767, 1.01);
    AutoScale autoScale3(&lowPassFilter3, -32768, 32767, 1.01);

    // Initialise analog input and tell it what fulters to use
    AnalogInFiltered ai1(&autoScale1, p20, DATA_CHANGE_TRIGGER);
    AnalogInFiltered ai2(&autoScale2, p19, DATA_CHANGE_TRIGGER);
    AnalogInFiltered ai3(&autoScale3, p18, DATA_CHANGE_TRIGGER);

    Thread _hid_thread(hid_thread);
    while (true) {
        // Measure analog in's
        ai1.setData(0);
        ai2.setData(0);
        ai3.setData(0);

        but5.measure();
        but6.measure();
        but7.measure();
        but8.measure();
        but9.measure();
        but10.measure();
        but11.measure();
        but12.measure();
        but13.measure();
        but14.measure();
        but15.measure();
        but16.measure();
        but17.measure();

        but21.measure();
        but22.measure();
        but23.measure();
        but24.measure();
        but25.measure();

        // test of any of the values have been changed, so we only update when data was actually changed
        const bool isChanged =
            ai1.getIsChanged() ||
            ai2.getIsChanged() ||
            ai3.getIsChanged() ||
            but5.getIsChanged() ||
            but6.getIsChanged() ||
            but7.getIsChanged() ||
            but8.getIsChanged() ||
            but9.getIsChanged() ||
            but10.getIsChanged() ||
            but11.getIsChanged() ||
            but12.getIsChanged() ||
            but13.getIsChanged() ||
            but14.getIsChanged() ||
            but15.getIsChanged() ||
            but16.getIsChanged() ||
            but17.getIsChanged() ||
            but21.getIsChanged() ||
            but22.getIsChanged() ||
            but23.getIsChanged() ||
            but24.getIsChanged() ||
            but25.getIsChanged();
        if (
            isChanged
        ) {
            // Copy analog data to global data
            analogValueMutex.lock();
            analogData.buttons.bit0 = but5.getData();
            analogData.buttons.bit1 = but6.getData();
            analogData.buttons.bit2 = but7.getData();
            analogData.buttons.bit3 = but8.getData();
            analogData.buttons.bit4 = but9.getData();
            analogData.buttons.bit5 = but10.getData();
            analogData.buttons.bit6 = but11.getData();
            analogData.buttons.bit7 = but12.getData();
            analogData.buttons.bit8 = but13.getData();
            analogData.buttons.bit9 = but14.getData();
            analogData.buttons.bit10 = but15.getData();
            analogData.buttons.bit11 = but16.getData();
            analogData.buttons.bit12 = but17.getData();
            analogData.buttons.bit13 = but21.getData();
            analogData.buttons.bit14 = but22.getData();
            analogData.buttons.bit15 = but23.getData();
            analogData.buttons.bit16 = but24.getData();
            analogData.buttons.bit17 = but25.getData();

            analogData.value1 = ai1.getData();
            analogData.value2 = ai2.getData();
            analogData.value3 = ai3.getData();
            analogData.value4 = 0.;
            analogValueMutex.unlock();

            // Signal that data has been changed
            _hid_thread.signal_set(0x1);
        }
        Thread::wait(1);
    }
}