Connecting a JR EA131 RC satellite receiver to an Arduino

Thanks to this blog post from Øystein I was finally able to connect a JR EA131 satellite receiver to an Arduino over SPI.
The blog post describes how to connect a Spectrum satellite receiver which is somehow similar. However, the JR receiver I have didn’t work with the SpectrumRC library from Øystein.
I’m not sure if this was because the JR EA131 is a DSM-J device (and not DSM2) or if it’s just another device revision.
Anyhow, I found out, that the satellite receiver indeed sends it data with 115200 8-N-1. But instead of 16 bytes per frame (starting with 0x03 0x01), the EA131 sends 32 bytes per frame. There are some kind of 16 bit start values each 16 bytes (0x00 0x46 in the screen shot), but unfortunately they seem to change every once in a while (at least when powering off/on the satellite receiver). So these cannot be used as easy way to determine the start of a frame.
It was fairly easy to reverse engineer the positions of the 7 channel values, though:

Each channel value has its own range:
THRO 0x8000 - 0x87ff
RUDD 0x1800 - 0x1fff
AILE 0x0800 - 0x0fff
ELEV 0x1000 - 0x17ff
GEAR 0x2000 - 0x27ff
AUX2 0x3000 - 0x37ff
FLAP 0x2800 - 0x2fff

With that information, I was able to write a “synchroniz()” function which is called at begin of a sketch. It reads the next 32 byte from the serial bus and looks for 3 of the 7 channel values according to their specific value range. That way, the data stream can be synchronized.

I wrote a small sketch to test the synchronization and value readout.
The values are stored in an array (channel), using a channelMap. The values are also remapped to go from 1000 … 2000 (decimal), which is the correct value range for use in the MultiWii firmware (which is where I plan to use the code eventually).

Please note, that I wrote the test sketch on a Arduino Duemillanove, using the “split SPI (Send > USB, Receive < TTL)” tip from Øystein.

It’s should be fairly easy to port it to an Arduino Mega with more than one Serial bus.

Here’s the test sketch:

/*********** RC alias *****************/
#define ROLL       0
#define PITCH      1
#define YAW        2
#define THROTTLE   3
#define AUX1       4
#define AUX2       5
#define CAMPITCH   6
#define CAMROLL    7
#define NA         8

int channelMap[] = {PITCH, AUX1, AUX2, NA, NA, NA, THROTTLE, NA, YAW, NA, NA, NA, NA, NA, ROLL, CAMPITCH};
unsigned int channels[9]={0};
int valueOffsetts[] = {0x0800, 0x1000, 0x1800, 0x8000, 0x2000, 0x3000, 0x2800, 0x0};
int channel=0;
boolean cByte=false;
unsigned int prevByte;

void setup()  
{
  pinMode(redLedPin, OUTPUT);
  Serial.begin(115200);
  
  boolean synched=false;
  for(int i=0;i<10&&!synched;i++)
    synched = synchronize();
  if(synched)
    Serial.println("Synchronized");
  else
    Serial.println("No Synch!");
}

void loop()
{
  while (Serial.available()) {
    unsigned int val = Serial.read();
      if(cByte) {
          channels[channelMap[channel]] = ((prevByte*256+val)-valueOffsetts[channelMap[channel]])/2+976;
        if(channel==15)
          showChannels();
        channel = (channel+1)%16;
      }
    prevByte = val;
    cByte = !cByte;
  } 
}

void showChannels()
{
  for(int i=0;i<8;i++)
  {
    switch(i)
    {
      case ROLL: Serial.print("ROLL: "); break;
      case PITCH: Serial.print(" PITCH: "); break;
      case YAW: Serial.print(" YAW: "); break;
      case THROTTLE: Serial.print(" THROTTLE: "); break;
      case AUX1: Serial.print(" AUX1: "); break;
      case AUX2: Serial.print(" AUX2: "); break;
      case CAMPITCH: Serial.print(" CAMPITCH: "); break;
      case CAMROLL: Serial.print(" CAMPROLL: "); break;
    }      

    Serial.print(channels[i]);
    Serial.print(" ");
  }
  Serial.println();
}

boolean synchronize()
{
    unsigned int syncBuff[32];
    
    // Reset the Serial ringbuffer
    // This gives this function more time to figure the start byte out
    // without meanwhile overflowing the ringbuffer in the background.
    Serial.flush();
    
    // Get the next 32 Bytes for an analysis
    int i = 0;
    while(i<32)
    {
      if(Serial.available())
         syncBuff[i++]=Serial.read();
    }
    
    // These are the valid value ranges of the ELEV, GEAR and AUX2 channels
    // which are transmitted in three sequential 16 bit values in a datablock
    unsigned int triggers[] = {0x1000, 0x17ff,    0x2000, 0x27ff,    0x3000, 0x37ff};

    // To find a well defined starting point in the byte stream
    // look for three 16 bit values which are fitting in these ranges respectively
    int startByte;
    boolean syncFound=false;
    for(startByte=0; startByte<32 && !syncFound; startByte++)
    {       
      syncFound = true;
      for(int trigger=0; trigger<3; trigger++)
      {
        int i1 = (startByte+trigger*2)%32;
        int i2 = (startByte+trigger*2+1)%32;
        unsigned int val = syncBuff[i1]*256+syncBuff[i2];        
        if(val < triggers[2*trigger] || val > triggers[2*trigger+1])
        {
          syncFound=false;
          break;
        }
      }
    }
    
    // A matching start byte has been found...
    if(syncFound)
    {
      
      // "remove" bytes from the Serial ringbuffer until the next
      // datablock start
      startByte--;
      while(startByte>0)
      {
          if(Serial.available()) {
           Serial.read();
           startByte--;
          }
      }
      
      // The next byte in the ringbuffer is now the first byte of the first 16 bit value
      // of the datablock (ELEV)
    }
    return syncFound;   
}