PC joystick to 8ch Tx conversion

3689466544?profile=originalI've been looking at my unused twist grip joystick for a while now thinking about hooking it up to a transmitter via the PPM trainer port.

I might go a step beyond this and get a new 2.4GHz Tx module and add that to the joystick to make it a transmitter.

Fly-Dream make an affordable and reliable 2.4GHz DIY module with three pins for GND,+,PPM in.

I've been able to generate PPM from an Arduino in the past so now it's a matter of hooking the pots in the joystick to the analog pins and the buttons to the digital pins.  The pots move over a portion of the total travel in a joystick so my Vref will need to be a bit above the max voltage the pot will register.  So if the pot only travels between 0 to half way and I feed the pot +5v then the max output voltage will be 2.5V.  

I've also ordered an 8ch 12bit ADC I2C breakout which samples at about 12KHz.  This will give me more leg room at reading resolution of 4095.  The high resolution reading then goes through expo / trim / end point / sample filtering maths and comes out at a resolution of 1024 per channel.  As far as I can tell the PPM output resolution does not need to be any higher than that, nor do I think the timing in an Arduino can get any better than that.

The hat switch can be coded to move the camera tilt servo.  Might even pop out the digital hat switch and put a Wii joystick in there for tilt rate control.  

With the 8 or more buttons on the joystick I should be able to code it to give me all the PWM levels needed for channel 6 modes on the Arducopter.

E-mail me when people leave their comments –

You need to be a member of diydrones to add comments!

Join diydrones


  • Sorry Francisco, paid work is pushing time on this to the side.  Planning a holiday soon so should get some more time on this.  I did start coding a Windows app to define all the menu's and navigation as well as an app to define all the model parameters which then get stored on the SD card.  Ultimately I would like to 3D print a complete controller setup with all these electronics built in rather than work with off the shelf gear like my Logitec PC joystick.  Custom made joystick with a 3D hall effect sensor would be the way to go.  Might even end up on Kickstarter.  If it is just the mixing you want to see the code for this may help -

    Probably going to be bits missing as I have just copied what I thought was relevant -

    #define INPUT_CHANNELS 12
    #define OUTPUT_CHANNELS 8

    const int PPM_Trigger = 380; // PPM frame fixed LOW phase
    const int PPM_FrLen = 22500;

    float Mixed_Output[8];
    int8_t selected_mixer=0;

    int Input_Position[16]; //Position of input control -2048 to 2048
    int Output_PWM[OUTPUT_CHANNELS]; //Final output PWM

    struct input_channel_settings {
    int Trim; //Range -500 to 500
    int Min; //Range -1000 to 1000
    int Max;
    int Low_Expo; //Range -1000 to 1000 (/100 -10 to 10)
    int High_Expo;
    boolean Reverse;

    struct output_channel_settings {
    int Low_Trim;
    int High_Trim;
    int Low_Min;
    int Low_Max;
    int High_Min;
    int High_Max;

    float PPM_Pre_Mix[INPUT_CHANNELS]; //input trim and expo applied

    struct mix_settings {
    int8_t SourceChannel;
    int8_t DestinationChannel;
    int8_t Mix_Amount;

    struct model_settings {
    char Name[16];
    int8_t Craft_Type;
    int8_t Fixed_Wing_Mix;
    int8_t Heli_Mix;
    int8_t Num_Input_Channels;
    int8_t Num_Output_Channels;
    struct input_channel_settings IN_CH[INPUT_CHANNELS];
    struct output_channel_settings OUT_CH[OUTPUT_CHANNELS];
    int8_t Mixer_Max;
    struct mix_settings Mixer[30];
    } Active_Model;

    float fscale( float originalMin, float originalMax, float newBegin, float newEnd, float inputValue, float curve){

    float OriginalRange = 0;
    float NewRange = 0;
    float zeroRefCurVal = 0;
    float normalizedCurVal = 0;
    float rangedValue = 0;
    boolean invFlag = 0;

    // condition curve parameter
    // limit range

    if (curve > 10) curve = 10;
    if (curve < -10) curve = -10;

    curve = (curve * -.1) ; // - invert and scale - this seems more intuitive - postive numbers give more weight to high end on output
    curve = pow(10, curve); // convert linear scale into lograthimic exponent for other pow function

    // Check for out of range inputValues
    if (inputValue < originalMin) {
    inputValue = originalMin;
    if (inputValue > originalMax) {
    inputValue = originalMax;

    // Zero Refference the values
    OriginalRange = originalMax - originalMin;

    if (newEnd > newBegin){
    NewRange = newEnd - newBegin;
    NewRange = newBegin - newEnd;
    invFlag = 1;

    zeroRefCurVal = inputValue - originalMin;
    normalizedCurVal = zeroRefCurVal / OriginalRange; // normalize to 0 - 1 float

    // Check for originalMin > originalMax - the math for all other cases i.e. negative numbers seems to work out fine
    if (originalMin > originalMax ) {
    return 0;

    if (invFlag == 0){
    rangedValue = (pow(normalizedCurVal, curve) * NewRange) + newBegin;

    else // invert the ranges
    rangedValue = newBegin - (pow(normalizedCurVal, curve) * NewRange);

    return rangedValue;

    void Process_Channels() {

    float Scaled_Trim;

    for (int8_t ch_num=0;ch_num<INPUT_CHANNELS;ch_num++) { //Invert input position if set to reverse
    if (Active_Model.IN_CH[ch_num].Reverse == true) {
    Input_Position[ch_num] = Input_Position[ch_num] * -1;

    //Mix channels
    int Mix_Count[8];

    //Init output to 0
    for (int8_t mix_num=0;mix_num<Active_Model.Mixer_Max;mix_num++) {
    if (!(Active_Model.Mixer[mix_num].SourceChannel==-1)) {
    Mixed_Output[Active_Model.Mixer[mix_num].DestinationChannel-1] = 0;

    //get number of mixes for each output channel
    for (int8_t mix_num=0;mix_num<Active_Model.Mixer_Max;mix_num++) {
    if (!(Active_Model.Mixer[mix_num].SourceChannel==-1)) {

    //accumulate mixed output for each mixer
    for (int8_t mix_num=0;mix_num<Active_Model.Mixer_Max;mix_num++) {
    if (!(Active_Model.Mixer[mix_num].SourceChannel==-1)) {
    Mixed_Output[Active_Model.Mixer[mix_num].DestinationChannel-1] += (((float)Input_Position[Active_Model.Mixer[mix_num].SourceChannel-1]/1000) * (((float)Active_Model.Mixer[mix_num].Mix_Amount/100) * (float)Mix_Count[Active_Model.Mixer[mix_num].DestinationChannel-1] * -1));

    for (int8_t ch=0;ch<OUTPUT_CHANNELS;ch++) { //Average out the settings
    Mixed_Output[ch] = (Mixed_Output[ch] / (float)Mix_Count[ch]);
    if (Mixed_Output[ch] > 1) { Mixed_Output[ch] = 1; }
    if (Mixed_Output[ch] < -1) { Mixed_Output[ch] = -1; }

    //Output Mixed channels to set PWM range and adjust output trims
    for (int8_t ch_num=0;ch_num<OUTPUT_CHANNELS;ch_num++) {

    if (Mixed_Output[ch_num] > 0) { // > 0
    if (DR_Low) {
    int Low_Range_Min_Amount = Active_Model.OUT_CH[ch_num].Low_Trim - Active_Model.OUT_CH[ch_num].Low_Min;
    Output_PWM[ch_num] = (int)fscale( 0, 1, Active_Model.OUT_CH[ch_num].Low_Trim, Active_Model.OUT_CH[ch_num].Low_Max, Mixed_Output[ch_num], 0);
    } else {
    Output_PWM[ch_num] = (int)fscale( 0, 1, Active_Model.OUT_CH[ch_num].High_Trim, Active_Model.OUT_CH[ch_num].High_Max, Mixed_Output[ch_num], 0);
    } else { // <= 0
    if (DR_Low) {
    Output_PWM[ch_num] = (int)fscale( -1, 0, Active_Model.OUT_CH[ch_num].Low_Min, Active_Model.OUT_CH[ch_num].Low_Trim, Mixed_Output[ch_num], 0);
    } else {
    Output_PWM[ch_num] = (int)fscale( -1, 0, Active_Model.OUT_CH[ch_num].High_Min, Active_Model.OUT_CH[ch_num].High_Trim, Mixed_Output[ch_num], 0);



  • Hey John have you complete the code???

  • Thanks John, i'll be waiting... If you need some help with the code... i'm here...

  • Once I have added the SD card reading code back into the project I'll upload to Google Code hosting.

  • Problem solved -

    had to use software SPI on the Mega by forcing it in Sd2Card.h.  Setting is :

    #define MEGA_SOFT_SPI 1

    Also, instead of the usual Mega SPI pins I had to connect them to these pins :

    SS 10, MOSI 11, MISO 12, and SCK 13

  • Love to... once I sort out this strange conflict between the SD library and my LCD_I2C library.  I created a stripped down sample of code with just the SD and LCD_I2C libraries and it appears that when either are used either the reading of the SD card file gets corrupted or the LCD screen text gets corrupted.  As soon as I disable one or the other they are fine on their own.  One is on I2C (A4/5) and the other is on the Mega SPI pins (50-53).  Compiles fine so possibly it's something to do with shared timers.  Just going to read up on it all now.

  • Hello John, i´m very interested in your system, could you share the codes:


  • In the interests of flexible mixing, I've now separated out channel input settings for trim, expo and reverse, and then output settings for their own trim and travel.  Input channels are called Thottle, Aileron, Elevator, Rudder, Aux1, Aux2, Aux3 and Aux4 and the output channels are just called CH1 to CH8.  The output settings are related to the physical servo trim and range at a low or high rate.  Input settings relate to the desired throttle, roll, pitch and yaw trim and expo.  I then have up to 30 mixers you can define to take, as an example, the aileron input and feed it 100% to CH2 and -100% to CH3 and elevator input set to 100%  CH2 and 100% CH3.  Since not all servo's are set up the same you can still individually set the output trim and travel.  I might even expand out the input channels to AUX5,6,7,8 and use the mixers to blend them with any of the 8 output channels.

    Then Mega board has heaps of digital and analog inputs so I could have a fine collection of switches and dials to hook into this thing.  Even some nice bright LED character displays.

    Also working on using the OpenLRS to as the telemetry connection and do away with the xBee's.  Combine that with the SD card module and I can record all the telemetry.  I will then use BT modules between the TX box and the Antenna tracker and let the mega tell the tracker where to point via wireless.

    So many cool things and so little time in life for fun.

  • This is the menu system I have been coding.

    Arduino Mega controlled RC Transmitter - LCD Menu system from John Grouse on Vimeo.

  • Ok, now using a Mega2560 for the main Tx processing and the Tx / Rx modules are now the HK OpenLRS modules.  The complex menu code I wrote ate too much RAM on the Nano.  Heaps of RAM free now on the 2560.

This reply was deleted.