Arduino as flight simulator interface for any rc receiver

Quadcopter 2 FMS model preview

Important Update

If you are going to use this code, please read on comments, you will find a better written code, shorter and with no conversions. Thanks to Fabien.

You might be interested also in :Quacopter hexcopter, optocopter FMS models

I have left my usb adapter for my RC Radio to flight simulator to my friend Simone.
I have a spare arduino (any arduino is suitable, until it as at least the usb port, obviously 🙂 ) and a spare turnigy receiver with damaged antenna (max range 5 meters, enough for the coach).
I don’t want to buy another usb adapter for flight simuator because i know it is possible to use the Arduino Uno to capture the RX signals and forward them to my pc through the usb port, so i can use my radio with the flight simulator, not just as usual, but wirelessly!

It is very easy, you do not even need schematics as it is quiet simple to wire all up.

Exluding pin 0 and 1 of the arduino (i’m using the Uno) start wiring your RX channel as follows:

RX Chan 1 to Arduino Pin2
RX Chan 2 to Arduino Pin3
RX Chan 3 to Arduino Pin4
RX Chan 4 to Arduino Pin5
RX Chan BATT + to Arduino 5V.
RX Chan BATT - to Arduino Gnd

We are talking about the signal cable (the white one) vcc and gnd are managed by the BATTERY cables.

Code for the Arduino Uno has been taken from this url http://philrobotics.com/forum/index.php?topic=74.0 , thanks to Philippines Robotics.
Just select all code and save it as an arduino sketch, then compile and upload it to your board.

#define RX_SIGNAL_TIMEOUT 15000 // 15mS
#define NUM_OF_RXCHANNELS 4

#define CHANNEL1_INPUT 2
#define CHANNEL2_INPUT 3
#define CHANNEL3_INPUT 4
#define CHANNEL4_INPUT 5

enum radio_states_e
{
POLLCHANNELS
,SENDDATA
};

enum radio_channels_e
{
CHANNEL1
,CHANNEL2
,CHANNEL3
,CHANNEL4
,DEFAULT_CH
};

enum radio_states_e enRadioStates;
enum radio_channels_e enRadioChannels;

unsigned long ulToggleTimer,ulPrevToggleTimer;
unsigned int uiPrevPinValue;
boolean blSignalTimeout;
byte bChannelData[NUM_OF_RXCHANNELS];

byte convertData(unsigned long ulElapsedTime)
{
ulElapsedTime = ulElapsedTime / 10;

if(ulElapsedTime > 250)
{
ulElapsedTime = 250;
}
else if(ulElapsedTime < 70)
{
ulElapsedTime = 70;
}
else
{
/* Do Nothing */
}

return (byte)ulElapsedTime;
}

void checkChannels()
{
unsigned int uiTemp;

switch(enRadioChannels)
{
case CHANNEL1:
{
// High to Low Transition of Channel 1
if((HIGH == uiPrevPinValue) && (LOW == digitalRead(CHANNEL1_INPUT)))
{
ulPrevToggleTimer = ulToggleTimer;
ulToggleTimer = micros(); // mark start of channel 2

#if 1
uiTemp = bChannelData[0] << 1;
uiTemp += bChannelData[0];
uiTemp += convertData(ulToggleTimer - ulPrevToggleTimer);

bChannelData[0] = (byte)(uiTemp >> 2);
#else
bChannelData[0] = convertData(ulToggleTimer - ulPrevToggleTimer);
#endif

enRadioChannels = CHANNEL3;
}
break;
}
case CHANNEL2:
{
// High to Low Transition of Channel 2
if((HIGH == uiPrevPinValue) && (LOW == digitalRead(CHANNEL2_INPUT)))
{
ulPrevToggleTimer = ulToggleTimer;
ulToggleTimer = micros(); // mark start of channel 3

#if 1
uiTemp = bChannelData[1] << 1;
uiTemp += bChannelData[1];
uiTemp += convertData(ulToggleTimer - ulPrevToggleTimer);

bChannelData[1] = (byte)(uiTemp >> 2);
#else
bChannelData[1] = convertData(ulToggleTimer - ulPrevToggleTimer);
#endif

enRadioChannels = CHANNEL4;
}
break;
}
case CHANNEL3:
{
// High to Low Transition of Channel 3
if((HIGH == uiPrevPinValue) && (LOW == digitalRead(CHANNEL3_INPUT)))
{
ulPrevToggleTimer = ulToggleTimer;
ulToggleTimer = micros(); // mark start of channel 4

#if 1
uiTemp = bChannelData[2] << 1;
uiTemp += bChannelData[2];
uiTemp += convertData(ulToggleTimer - ulPrevToggleTimer);

bChannelData[2] = (byte)(uiTemp >> 2);
#else
bChannelData[2] = convertData(ulToggleTimer - ulPrevToggleTimer);
#endif

enRadioChannels = CHANNEL2;
}
break;
}
case CHANNEL4:
{
// High to Low Transition of Channel 4
if((HIGH == uiPrevPinValue) && (LOW == digitalRead(CHANNEL4_INPUT)))
{
ulPrevToggleTimer = ulToggleTimer;
ulToggleTimer = micros();

#if 1
uiTemp = bChannelData[3] << 1;
uiTemp += bChannelData[3];
uiTemp += convertData(ulToggleTimer - ulPrevToggleTimer);

bChannelData[3] = (byte)(uiTemp >> 2);
#else
bChannelData[3] = convertData(ulToggleTimer - ulPrevToggleTimer);
#endif

uiPrevPinValue = LOW;
enRadioChannels = DEFAULT_CH;
blSignalTimeout = false;
enRadioStates = SENDDATA;
}
break;
}
default:
{
/* Do Nothing */
break;
}
}
}

void setup()
{
Serial.begin(9600);
ulToggleTimer = micros();

pinMode(CHANNEL1_INPUT,INPUT);
pinMode(CHANNEL2_INPUT,INPUT);
pinMode(CHANNEL3_INPUT,INPUT);
pinMode(CHANNEL4_INPUT,INPUT);

enRadioStates = POLLCHANNELS;
enRadioChannels = DEFAULT_CH;
}

void loop()
{
byte bTemp;

switch(enRadioStates)
{
/* Poll Channels */
case POLLCHANNELS:
{
/* Check Channel 1 High to Low Transition */
if(blSignalTimeout)
{
/* Low to High Transition of Channel 1 */
if((LOW == uiPrevPinValue) && (HIGH == digitalRead(CHANNEL1_INPUT)))
{
ulToggleTimer = micros(); // mark start of channel 1
uiPrevPinValue = HIGH;
enRadioChannels = CHANNEL1; // poll Channel 1 first
}

checkChannels();
}
/* Wait for Timeout */
else if(false == digitalRead(CHANNEL1_INPUT))
{
if((micros() - ulToggleTimer) >= RX_SIGNAL_TIMEOUT)
{
blSignalTimeout = true;
uiPrevPinValue = LOW;
enRadioChannels = DEFAULT_CH;
}
}
else
{
ulToggleTimer = micros(); // mark timeout start
}
break;
}
/* Send Data */
case SENDDATA:
{
// 0xF0 + NUM_OF_RXCHANNELS
Serial.write(0xF0+NUM_OF_RXCHANNELS);

// 0x00
Serial.write((byte)0x00);

// 100 = 1mS, 200 = 2mS.
for(bTemp = 0; bTemp < NUM_OF_RXCHANNELS; bTemp++)
{
Serial.write(bChannelData[bTemp]);
}

enRadioStates = POLLCHANNELS;
ulToggleTimer = micros(); // mark timeout start
break;
}
default:
{
/* error state, must not be reached */
break;
}
}
}

The correct interface for FMS is “Serial PIC-Interface” – 9600 Baud / 0xF0 + Sync

If you want to add multirotor models to your FMS installation, visit this link, download models you want, then extract the zip package content to the “models” directory inside the FMS folder within program files. 

Incoming search terms:

  • arduino rc simulator (22)
  • arduino (17)
  • arduino rc receiver (17)
  • arduino rc receiver interface (15)
  • gy-65 arduino (15)
  • arduino flight simulator (14)
  • r/c receiver usb (10)
  • arduino rc usb interface (4)
  • diy spektrum rc sim (3)
  • arduino rc simulator usb adapter (3)
(Visited 18,979 times, 9 visits today)

Author: Giuseppe Urso

Giuseppe lives in Haarlem now with his shiny dog, Filippa In 1982 received his first home computer, a Commodore 64, followed by Datasette and a 1541 Floppy Disk Drive. In 1999 he installed his first Linux distro (LRH6). In 2006 he switched to Debian as favourite OS. Giuseppe Urso actively sustains the Free Software Fundation and his founder Richard Mattew Stallman, he speaks to people trying to convince them to join the fight now, and about how important is to use Free Software only. He has a job as Infra Specialist at Hippo Enterprise Java Cms an Open Source Enterprise class Content Management System, one of the coolest company ever, in Amsterdam. He's always ready to install Debian on other people computers for free.

28 thoughts on “Arduino as flight simulator interface for any rc receiver”

  1. Hi, I would like to make a similar setup to capture signal from 6 channels… Is there any reason you didn’t use inputs 0 & 1, or did you simply not need them?
    By the way, thank you for sharing your projects… It helps a lot for a complete beginner like me!

    1. Hi Fabien,
      I left chan 0 and 1 alone as they are tx-rx…
      About 6 channel: i think you should modify the rest of the code accordingly.
      by the way, which flight sim are you using?
      Ciao!!

      1. It’s not for use with a simulator, but for a setup which is controlled through a computer…
        I also read the code, and since there is no reason not to use channels 0 and 1, I will change the code in order to read them, as you suggest!

        Thanks!

    1. I don’t mind sharing… but I’m rewriting the program completely, since I discovered the pulseIn function, which will allow me to significantly simplify it.
      I’ll show you the results!

  2. hi, I tried to add the channel 5 and 6, but got no success.
    just added two more channels to the same code.

    someone could add more channels? I wish I could enable gears and flaps.

    follows what changed following the same logic, but I may have faltered at some point.

    #define RX_SIGNAL_TIMEOUT 15000 // 15mS
    #define NUM_OF_RXCHANNELS 6

    #define CHANNEL1_INPUT 17
    #define CHANNEL2_INPUT 12
    #define CHANNEL3_INPUT 13
    #define CHANNEL4_INPUT 14
    #define CHANNEL5_INPUT 15
    #define CHANNEL6_INPUT 16

    enum radio_states_e
    {
    POLLCHANNELS
    ,SENDDATA
    };

    enum radio_channels_e
    {
    CHANNEL1
    ,CHANNEL2
    ,CHANNEL3
    ,CHANNEL4
    ,CHANNEL5
    ,CHANNEL6
    ,DEFAULT_CH
    };

    enum radio_states_e enRadioStates;
    enum radio_channels_e enRadioChannels;

    unsigned long ulToggleTimer,ulPrevToggleTimer;
    unsigned int uiPrevPinValue;
    boolean blSignalTimeout;
    byte bChannelData[NUM_OF_RXCHANNELS];

    byte convertData(unsigned long ulElapsedTime)
    {
    ulElapsedTime = ulElapsedTime / 10;

    if(ulElapsedTime > 250)
    {
    ulElapsedTime = 250;
    }
    else if(ulElapsedTime < 70)
    {
    ulElapsedTime = 70;
    }
    else
    {
    /* Do Nothing */
    }

    return (byte)ulElapsedTime;
    }

    void checkChannels()
    {
    unsigned int uiTemp;

    switch(enRadioChannels)
    {
    case CHANNEL1:
    {
    // High to Low Transition of Channel 1
    if((HIGH == uiPrevPinValue) && (LOW == digitalRead(CHANNEL1_INPUT)))
    {
    ulPrevToggleTimer = ulToggleTimer;
    ulToggleTimer = micros(); // mark start of channel 2

    #if 1
    uiTemp = bChannelData[0] <> 2);
    #else
    bChannelData[0] = convertData(ulToggleTimer - ulPrevToggleTimer);
    #endif

    enRadioChannels = CHANNEL3;
    }
    break;
    }
    case CHANNEL2:
    {
    // High to Low Transition of Channel 2
    if((HIGH == uiPrevPinValue) && (LOW == digitalRead(CHANNEL2_INPUT)))
    {
    ulPrevToggleTimer = ulToggleTimer;
    ulToggleTimer = micros(); // mark start of channel 3

    #if 1
    uiTemp = bChannelData[1] <> 2);
    #else
    bChannelData[1] = convertData(ulToggleTimer - ulPrevToggleTimer);
    #endif

    enRadioChannels = CHANNEL4;
    }
    break;
    }
    case CHANNEL3:
    {
    // High to Low Transition of Channel 3
    if((HIGH == uiPrevPinValue) && (LOW == digitalRead(CHANNEL3_INPUT)))
    {
    ulPrevToggleTimer = ulToggleTimer;
    ulToggleTimer = micros(); // mark start of channel 4

    #if 1
    uiTemp = bChannelData[2] <> 2);
    #else
    bChannelData[2] = convertData(ulToggleTimer - ulPrevToggleTimer);
    #endif

    enRadioChannels = CHANNEL5;
    }
    break;
    }
    case CHANNEL4:
    {
    // High to Low Transition of Channel 4
    if((HIGH == uiPrevPinValue) && (LOW == digitalRead(CHANNEL4_INPUT)))
    {
    ulPrevToggleTimer = ulToggleTimer;
    ulToggleTimer = micros(); // mark start of channel 5

    #if 1
    uiTemp = bChannelData[3] <> 2);
    #else
    bChannelData[3] = convertData(ulToggleTimer - ulPrevToggleTimer);
    #endif

    enRadioChannels = CHANNEL6;
    }
    break;
    }
    case CHANNEL5:
    {
    // High to Low Transition of Channel 5
    if((HIGH == uiPrevPinValue) && (LOW == digitalRead(CHANNEL5_INPUT)))
    {
    ulPrevToggleTimer = ulToggleTimer;
    ulToggleTimer = micros(); // mark start of channel 6

    #if 1
    uiTemp = bChannelData[4] <> 2);
    #else
    bChannelData[4] = convertData(ulToggleTimer - ulPrevToggleTimer);
    #endif

    enRadioChannels = CHANNEL2;
    }
    break;
    }
    case CHANNEL6:
    {
    // High to Low Transition of Channel 6
    if((HIGH == uiPrevPinValue) && (LOW == digitalRead(CHANNEL4_INPUT)))
    {
    ulPrevToggleTimer = ulToggleTimer;
    ulToggleTimer = micros();

    #if 1
    uiTemp = bChannelData[5] <> 2);
    #else
    bChannelData[5] = convertData(ulToggleTimer - ulPrevToggleTimer);
    #endif

    uiPrevPinValue = LOW;
    enRadioChannels = DEFAULT_CH;
    blSignalTimeout = false;
    enRadioStates = SENDDATA;
    }
    break;
    }
    default:
    {
    /* Do Nothing */
    break;
    }
    }
    }

    void setup()
    {
    Serial.begin(9600);
    ulToggleTimer = micros();

    pinMode(CHANNEL1_INPUT,INPUT);
    pinMode(CHANNEL2_INPUT,INPUT);
    pinMode(CHANNEL3_INPUT,INPUT);
    pinMode(CHANNEL4_INPUT,INPUT);
    pinMode(CHANNEL5_INPUT,INPUT);
    pinMode(CHANNEL6_INPUT,INPUT);

    enRadioStates = POLLCHANNELS;
    enRadioChannels = DEFAULT_CH;
    }

    void loop()
    {
    byte bTemp;

    switch(enRadioStates)
    {
    /* Poll Channels */
    case POLLCHANNELS:
    {
    /* Check Channel 1 High to Low Transition */
    if(blSignalTimeout)
    {
    /* Low to High Transition of Channel 1 */
    if((LOW == uiPrevPinValue) && (HIGH == digitalRead(CHANNEL1_INPUT)))
    {
    ulToggleTimer = micros(); // mark start of channel 1
    uiPrevPinValue = HIGH;
    enRadioChannels = CHANNEL1; // poll Channel 1 first
    }

    checkChannels();
    }
    /* Wait for Timeout */
    else if(false == digitalRead(CHANNEL1_INPUT))
    {
    if((micros() - ulToggleTimer) >= RX_SIGNAL_TIMEOUT)
    {
    blSignalTimeout = true;
    uiPrevPinValue = LOW;
    enRadioChannels = DEFAULT_CH;
    }
    }
    else
    {
    ulToggleTimer = micros(); // mark timeout start
    }
    break;
    }
    /* Send Data */
    case SENDDATA:
    {
    // 0xF0 + NUM_OF_RXCHANNELS
    Serial.write(0xF0+NUM_OF_RXCHANNELS);

    // 0x00
    Serial.write((byte)0x00);

    // 100 = 1mS, 200 = 2mS.
    for(bTemp = 0; bTemp < NUM_OF_RXCHANNELS; bTemp++)
    {
    Serial.write(bChannelData[bTemp]);
    }

    enRadioStates = POLLCHANNELS;
    ulToggleTimer = micros(); // mark timeout start
    break;
    }
    default:
    {
    /* error state, must not be reached */
    break;
    }
    }
    }

    1. Let me check as soon as i can, just can you please tell me which flight simulator you will use this with? It’s just to check for 6 chan since i used FMS and i can’t remember settings for 5th and 6th channel.
      Let me know how it goes and if you read some comment before yours, there is Fabien who is working on the very same aspect: adding 2 channels. Also he told me he’s going to rewrite the code and he will probably share it as he fnishes. I hope to get news from France soon! 🙂
      ciao!

      1. When I tested the code with my receiver (without any modification to the code) it could not read any of the channels…
        I don’t know if changes anything, but it’s a Futaba receiver (15ms cycle, pulse duration from 1000 to 2000 us).
        So I wrote this code so that I can read pulse signal from several digital inputs… they only have to be connected to consecutive pins :

        #define RX_SIGNAL_TIMEOUT 15000 // 15mS

        //Define Channel/Imput association
        #define CHANNEL1_INPUT 2
        #define NUM_OF_RXCHANNELS 6

        unsigned long duration;

        void setup()
        {
        Serial.begin(9600);

        for(int i = CHANNEL1_INPUT; i < NUM_OF_RXCHANNELS + CHANNEL1_INPUT; i++)
        {
        pinMode(i,INPUT);
        }
        }

        void loop()
        {
        for(int i = CHANNEL1_INPUT; i < NUM_OF_RXCHANNELS + CHANNEL1_INPUT; i++)
        {
        duration = pulseIn(i,HIGH,RX_SIGNAL_TIMEOUT);
        Serial.print(duration);
        Serial.print("\r");
        }
        Serial.print("E\r");
        }

        It's really short, but it does what I need : it gives the duration of a pulse for every channel, with a precision of ~8us (based on my arduino specs).
        As you can see, pulsein is REALLY useful!! See : http://arduino.cc/en/Reference/PulseIn

        1. I’m very happy to hear from you! Actually I imagined you were working on something simpler.
          I’m surprised for the fact that it could not read channels from futaba, so now i’m curious to try yours 🙂
          I have tro try this one asap! (i have an Er9x, JR clone).
          Will post results (maybe a small video too) soon.
          Thank you very much for sharing this, with no dubts, improvement.
          Ciao!!

    2. Hi
      as i told you, Fabien managed to rewrite the entire code in a more effective way and also using a different approach he surprisingly shortened the program to a few lines.
      You can find it some comments below this one.
      Hope it helps! and let us know how it works!
      Ciao

  3. hi, I launched here and read it yes, but in the serial output is not the same output as the first code, that would be the Serial PIC-Interface 0xF0.
    therefore, it is necessary to “PPJoy” recognize the signal.

    1. It seems like the original code didn’t include any commands that would make the arduino board to be recognized as a game controller…
      You can change ths :

      void loop()
      {
      for(int i = CHANNEL1_INPUT; i < NUM_OF_RXCHANNELS + CHANNEL1_INPUT; i++)
      {
      duration = pulseIn(i,HIGH,RX_SIGNAL_TIMEOUT);
      Serial.print(duration);
      Serial.print("\r");
      }
      Serial.print("E\r");
      }

      to this :

      void loop()
      {
      // 0xF0 + NUM_OF_RXCHANNELS
      Serial.write(0xF0+NUM_OF_RXCHANNELS);

      // 0x00
      Serial.write((byte)0x00);

      for(int i = CHANNEL1_INPUT; i < NUM_OF_RXCHANNELS + CHANNEL1_INPUT; i++)
      {
      // 100 = 1mS, 200 = 2mS.
      duration = pulseIn(i,HIGH,RX_SIGNAL_TIMEOUT) / 10;
      Serial.write(duration);
      }
      }

      This code should have the same output as the first code

      1. sure, it’s ok, but something happens and reading channel is incorrect, I do not know what’s happening …
        In another previous code, it reads and plays normally, despite only having achieved all 4 channels.

  4. Hello Giuseppe,

    I simplified the original program and made it read up to 8 channels. I tested it with the Turnigy 9x TX/RX and it works fine with FMS:

    #define RX_SIGNAL_TIMEOUT 15000 // 15mS

    //Define Channel/Input association
    #define CHANNEL1_INPUT 2
    #define NUM_OF_RXCHANNELS 8

    // data for each channel
    byte channelData[NUM_OF_RXCHANNELS];

    // order that the channels are sent
    unsigned int channelOrder[8] = {0, 2, 1, 3, 4, 5, 6, 7};

    void setup()
    {
    Serial.begin(9600);

    for(int i = 0; i 2500L)
    {
    return 250;
    }
    else if(t < 700L)
    {
    return 70;
    }

    return (byte)(t / 10);
    }

    void loop()
    {
    for(int c = 0; c < NUM_OF_RXCHANNELS; c++)
    {
    unsigned int channel = channelOrder[c];

    // read pulse duration
    while(digitalRead(channel+CHANNEL1_INPUT)==LOW);
    unsigned long t = micros();
    while(digitalRead(channel+CHANNEL1_INPUT)==HIGH);
    unsigned long duration = micros()-t;

    // using average of four readings
    // slower response but with less noise
    unsigned int data = channelData[channel] <>2);
    }

    // send PPM data to FMS
    // 0xF0 + NUM_OF_RXCHANNELS
    Serial.write(0xF0+NUM_OF_RXCHANNELS);

    // 0x00
    Serial.write((byte)0x00);

    // channel info
    for(int channel = 0; channel < NUM_OF_RXCHANNELS; channel++)
    {
    Serial.write(channelData[channel]);
    }
    }

    1. Hi because link for download your update code is not available for a long time, can you send me code on mail?
      Thanks.

  5. Hi @all

    first of all, great work you did here.
    I tried to use this for my Spektrum DX6i with AR6200 RX but no luck to get this working.

    The AR6200 is a DSM2 RX. Does someone here have any experience or interest in helping me getting this to work ?

    Would be a great learning project for me 😉

    Best regards

    Marcus

  6. Hello, I have dumb and stupid question.

    I want to test this setup, I’m using Henglong Sprint (27MHz TX/RX for RC cars).

    It looks like when I run the code, I didnt see any reaction in the FMS.

    Com8 is used by arduino IDE, the same com8 9600 is selected in FMS.

    Com8 port is locked from the point of view arduino IDE or putty in the time of FMS is running.

    Using arduino examples (“Input Pullup Serial”) and putty term, I see a “1” value constantly when RX channel 1 signal is not plugged into Arduino pin 2, “0” value if a unpaired RX ch 1 is plugged into arduino pin 2, quickly changing “1”and “0” values if RX is connected to pin2 of arduino and RX is paired with TX.

    So i suppose TX+RX are paired and some signalisation is coming on the ch 1 pluged into arduino pin2.

    I found more variations of this code on the net , all are similiar from my point of view, but no one was working for me.

    So from my point of view, there is something strange with signal modulation on RX,
    or with FMS software.

    I suppose that all RX send PWM signal to servos, is it possible that mine is a different
    kind of ? Because otherwise I need other computer with other OS and same FMS to test this setup again.

  7. I have a flysky-fst6 2.4ghz with fs-r6b receiver, and only 2 channels works, the problem inst the FMS, because when i open the serial monitor only 2 channels works. the receiver and transmitter are ok becaus i use them with my quadcpter.

    HELP ME PLEASE

    1. Hi there, it’s been a long time now that i don’t use it any more, but assuming all the hardware is working, afaict, that seems to be something either in the code or in the wiring. double check the wiring, replace the cables used…

      1. i tryed everyting but it didnt work for me, i dont know if im missing something really dumb i just did the wiring copy the code and uploaded it do my arduino uno and doesent work in freerider, please help me and thankyou for all your work
        PS sorry for bad english

Leave a Reply

Your email address will not be published. Required fields are marked *