Need help decoding incoming byte

#include <MsTimer2.h>

#include <Arduino.h>

// FMS PIC protocol for connecting a PPM stream to a PC.
// http://modelsimulator.com/
//
// Implements the 19200 baud FMS PIC protocol
// Converted from code by
// Richard.Prinz@MIN.at 3.2013
//
// This sketch will find the syncpulse and encode it as 0xFF, and encode
// the individual channels in sequence with the proportional value for each
// channel from 0x00 to 0xFE.
// The code will continue to encode channels regardless of their number
// until the next syncpulse.
//
// The PPM sigmal from the transmitter (or receiver, if it can suply a PPM signal)
// is connected to pin 3, external interupt 1. The Arduino board communicates with
// the computer through the serial port, thus being able to connect throug USB
// interface.
//
// The code uses the timer interrupt through the MsTimer2 library. This can be omitted
// if not wanted. It just adds the capability to send a "heartbeat" signal to the
// simulator if no PPM signal is detected for a while.
// http://playground.arduino.cc/Main/MsTimer2
//
// by Reynir Siik
// 2015-MAR-14, Pi day
//

#define MIN_MS        1000    //1000ul
/*
#define MID_MS            1500
#define MAX_MS            2000
#define RANGE_MS          1000
*/
#define CO                3.9215686
// 1000/255 == 3.9215686
  uint32_t serial_timer;
// Variables used in interupt routine is declared volatile to survive task switching
volatile boolean nextPtr = 0;
volatile unsigned long time[2];
volatile boolean sem = false;
volatile byte numWDT = 0;
int ppm[6];
void flash() {
  static boolean output = HIGH;
  digitalWrite(17, output);
  output = !output;
  
}


void setup()
{
  Serial.begin(19200);
  MsTimer2::set(100, ISR_WDTimer);   // 100 ms timeout
  //MsTimer2::set(1000, flash);
  MsTimer2::start();
  attachInterrupt(1, ISR1, FALLING); //Trigg on negative edge on pin 3
  pinMode(13, OUTPUT);
  pinMode(17, OUTPUT);
  

  /*
  PPM pulse train:
   __   ___________________________________   _____   ____   _______   ___ //___   __________   __ HIGH
     |_|      Sync pulse > 3000 us         |_|     |_|    |_|       |_|   //    |_|          |_|
                                               Channel pulse 1000 to 2000 us                       LOW
   Pulse start and end on falling edge. The end of one pulse is the begining of next.
   Sync pulse translates to FF
   Channel pulse length is encoded in the range 00 to FE, 1000 us is 00 and 2000 us is FE
   */
}

void loop()
{
  if (sem == true)

  {
    sem = false;
    int r = (time[!nextPtr] - time[nextPtr] - MIN_MS);
    if (r > 3000)       //sync pulse code 255
      r = 255;
             
    else {              //channels encoded in range 0-254
      r = (int)(r / CO);
      if (r > 254)
        r = 254;
        // pinMode(13, OUTPUT);

    }

    Serial.write(r);    // output FMS protocol
  // Serial.println(r);
  


    numWDT = 0;
  }

  if (numWDT > 2)       // watch dog timeout
  {
    numWDT = 0;
    Serial.write(255);  // output FMS protocol
  }
}

/* Pulse timing interupt routine */
void ISR1()
{
  time[nextPtr] = micros();
  nextPtr = !nextPtr;
  sem = true;
}

/* Watch dog timer interupt routine */
void ISR_WDTimer() {
  numWDT++;
}

 
      serial_timer = nowtime + 50;
    }
  }

That’s the code I’m running on a Sparkfun Pro Micro. I have my RC receiver PPM output connected to the Arduino. It works great with crrcsim, an opensource RC flight simulator. What I want to do is use that Arduino serial output as an input method for processing. It’s 6 channels. It reads the falling edge of the pulses and counts the time between falling edges. When I output it in processing, I see the values when I println(inByte).

What I’m thinking is that I need to read the data into an array with each channel being an element of the array. Basically, a 255 value is the sync pulse followed by the 6 channel values.

Here’s what I tried in processing and it doesn’t work right.

/**
 * Serial Duplex 
 * by Tom Igoe. 
 * 
 * Sends a byte out the serial port when you type a key
 * listens for bytes received, and displays their value. 
 * This is just a quick application for testing serial data
 * in both directions. 
 */


import processing.serial.*;

Serial myPort;      // The serial port

int inByte[] = new int[1]; //-1;    // Incoming serial data
int Channel[] = new int[inByte.length+5]; 


void setup() {
  size(400, 300);
  // create a font with the third font available to the system:
  PFont myFont = createFont(PFont.list()[2], 14);
  textFont(myFont);

  // List all the available serial ports:
  printArray(Serial.list());

  // I know that the first port in the serial list on my mac
  // is always my  FTDI adaptor, so I open Serial.list()[0].
  // In Windows, this usually opens COM1.
  // Open whatever port is the one you're using.
  String portName = Serial.list()[0];
  myPort = new Serial(this, portName, 19200);
}

void draw() {
  background(0);
 // text("Last Received: " + inByte, 10, 130);
  while(inByte[0] !=255){
    text("Last Received " + inByte, 10, 130);
    for(int i=0;i<6;i++){
        Channel[i] = inByte[0];
        println(i,Channel[0],i,Channel[1],i,Channel[2]);
    }

  }
}

void serialEvent(Serial myPort) {
    inByte[0] = myPort.read();

}

How do I read the inByte into an array that has 6 elements of only channel values?

Have you tried using splice()? Supposedly it copies one array to another.
Could also try converting the inByte array to a string and then using split() to create a second array.

1 Like

try this example see how receiving an sending then mange your code

// This example code is in the public domain.
import processing.serial.*;
int bgcolor;   // Background color
int fgcolor;   // Fill color
Serial myPort;      // The serial port
int[] serialInArray = new int[3]; // Where we'll put what we receive
int serialCount = 0;     // A count of how many bytes we receive
int xpos, ypos;     // Starting position of the ball
boolean firstContact = false;  // Whether we've heard from the
          // microcontroller
void setup() {
 size(256, 256); // Stage size
 noStroke();  // No border on the next thing drawn
 // Set the starting position of the ball (middle of the stage)
 xpos = width/2;
 ypos = height/2;
 // Print a list of the serial ports, for debugging purposes:
 println(Serial.list());
 // I know that the first port in the serial list on my mac
 // is always my FTDI adaptor, so I open Serial.list()[0].
 // On Windows machines, this generally opens COM1.
 // Open whatever port is the one you're using.
 String portName = Serial.list()[0];
 myPort = new Serial(this, portName, 9600);
}
void draw() {
 background(bgcolor);
 fill(fgcolor);
 // Draw the shape
 ellipse(xpos, ypos, 20, 20);
}
void serialEvent(Serial myPort) {
 // read a byte from the serial port:
 int inByte = myPort.read();
 // if this is the first byte received, and it's an A,
 // clear the serial buffer and note that you've
 // had first contact from the microcontroller.
 // Otherwise, add the incoming byte to the array:
 if (firstContact == false) {
 if (inByte == 'A') {
  myPort.clear();   // clear the serial port buffer
  firstContact = true;  // you've had first contact from the microcontroller
  myPort.write('A');  // ask for more
 }
 }
 else {
 // Add the latest byte from the serial port to array:
 serialInArray[serialCount] = inByte;
 serialCount++;
 // If we have 3 bytes:
 if (serialCount > 2 ) {
  xpos = serialInArray[0];
  ypos = serialInArray[1];
  fgcolor = serialInArray[2];
  // print the values (for debugging purposes only):
  println(xpos + "t" + ypos + "t" + fgcolor);
  // Send a capital A to request new sensor readings:
  myPort.write('A');
  // Reset serialCount:
  serialCount = 0;
 }
 }
}

arduino

/*
 Serial Call and Response
 Language: Wiring/Arduino
 This program sends an ASCII A (byte of value 65) on startup
 and repeats that until it gets some data in.
 Then it waits for a byte in the serial port, and
 sends three sensor values whenever it gets a byte in.
 Thanks to Greg Shakar and Scott Fitzgerald for the improvements
 The circuit:
 * potentiometers attached to analog inputs 0 and 1
 * pushbutton attached to digital I/O 2
 Created 26 Sept. 2005
 by Tom Igoe
 modified 24 April 2012
 by Tom Igoe and Scott Fitzgerald
 This example code is in the public domain.
 http://www.arduino.cc/en/Tutorial/SerialCallResponse
 */
int firstSensor = 0; // first analog sensor
int secondSensor = 0; // second analog sensor
int thirdSensor = 0; // digital sensor
int inByte = 0;   // incoming serial byte
void setup()
{
 // start serial port at 9600 bps:
 Serial.begin(9600);
 while (!Serial) {
 ; // wait for serial port to connect. Needed for Leonardo only
 }
 pinMode(2, INPUT); // digital sensor is on digital pin 2
 establishContact(); // send a byte to establish contact until receiver
      // responds
}
void loop()
{
 // if we get a valid byte, read analog ins:
 if (Serial.available() > 0) {
 // get incoming byte:
 inByte = Serial.read();
 // read first analog input, divide by 4 to make the range 0-255:
 firstSensor = analogRead(A0)/4;
 // delay 10ms to let the ADC recover:
 delay(10);
 // read second analog input, divide by 4 to make the range 0-255:
 secondSensor = analogRead(1)/4;
 // read switch, map it to 0 or 255L
 thirdSensor = map(digitalRead(2), 0, 1, 0, 255);
 // send sensor values:
 Serial.write(firstSensor);
 Serial.write(secondSensor);
 Serial.write(thirdSensor);
 }
}
void establishContact() {
 while (Serial.available() <= 0) {
 Serial.print('A'); // send a capital A
 delay(300);
 }
}

Thanks for the hints! It’s strange, what I have now fills Channel[ ] with the same value for each of the 6 index positions, then fills each index position with the next channel. Channel[0,1,2,3,4,5] is actually Channel[0], then it fills Channel[0,1,2,3,4,5 ] with Channel[1]…

void serialEvent(Serial myPort) {
    inByte[0] = myPort.read();
    if(inByte[0] != 255){ 
      for (int i=0;i<6;i++){
          Channel = splice(Channel,inByte[0],i);
          println(i,Channel[i]);
      } 
   }
}

Here’s what I mean. 0 - 5 are all the same value, then it is the next value that occupies 0 - 5. There must be some way to do this so that 0 - 5 actually represent Channels 1 through 6.

hints

Have you tried:
Channel = splice(Channel,inByte,i);

My second choice would be:
Channel = splice(Channel,inByte[i],i);

If that doesn’t work println the inByte array and see how the data is being received.

I have something now that almost works. It reads the inByte, looks for the sync byte value 255, then uses splice and some weird counting to build an array that has 7 elements. The problem now is the Channels kind of shift one position with each iteration. For instance, the Channel[1] value will show up in Channel[2], then the next time through it moves to Channel[3] etc, until it shows up in Channel[6] and then it repeats.

  void serialEvent(Serial myPort) {
//count = 0;
inByte[0] = myPort.read();

          if (count < 7) {

            if (inByte[0] == 255 ) {
           // Channel = splice(Channel,inByte[0],0);
           Channel[0] = 255;
            }else {
              if (Channel[0] == 255){
             Channel = splice(Channel,inByte[0],count+1); 
           //  Channel = append(Channel,inByte[0]);
           
              }
            }
       for(int i=0;i<7;i++) {
       println(i,Channel[i]);
       }       
       count++;
    }
    
   count = 0;    
  }

Can’t use anything but inByte[0] because it is an array of one element. I made it an array to match the type for Channel[].

I dug into the crrcsim source code and found code that does in c++ what I want to do in processing. It works with the simulator, so why not see how it has been done. The first thing I noticed is that it uses case statements and it tests for the number of channels and whether the incoming byte is larger or smaller than the sync byte to increment a channel number counter. That kinda confirms what I was seeing in my code, sometimes things were in “sync” and sometimes they shifted.

Here’s the simulator code for decoding the bytes:

/*
 * CRRCsim - the Charles River Radio Control Club Flight Simulator Project
 *   Copyright (C) 2005, 2007, 2008 - Jan Reucker (original author)
 *   Copyright (C) 2007, 2008 - Jens Wilhelm Wulf
 *   Copyright (C) 2008 - Todd Templeton
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */
#include "inputdev_serpic.h"

#define DEFAULT_SYNC_BYTE_19200       (0xFF)
#define DEFAULT_SYNC_BYTE_9600        (0xF0)
#define DEFAULT_SYNC_BYTE             DEFAULT_SYNC_BYTE_19200
#define DEFAULT_BUTTON_CHANNEL_19200  (0)
#define DEFAULT_BUTTON_CHANNEL_9600   (1)
#define DEFAULT_BUTTON_CHANNEL        DEFAULT_BUTTON_CHANNEL_19200


/**
 *  The constructor.
 *
 */
T_TX_InterfaceSerPIC::T_TX_InterfaceSerPIC()
  : T_TX_InterfaceSerial(),
    iSyncState(WAIT_SYNC), iSyncCounter(0),
    iSPIC_NumChannels(0), iSPIC_ButtonChannel(DEFAULT_BUTTON_CHANNEL),
    iSPIC_ChanCount(0), ucSyncByte(DEFAULT_SYNC_BYTE)
{
#if DEBUG_TX_INTERFACE > 0
  std::cout << "T_TX_InterfaceSerPIC::T_TX_InterfaceSerPIC ()\n";
#endif
}


/**
 *  The destructor.
 *
 */
T_TX_InterfaceSerPIC::~T_TX_InterfaceSerPIC()
{
#if DEBUG_TX_INTERFACE > 0
  std::cout << "T_TX_InterfaceSerPIC::~T_TX_InterfaceSerPIC ()\n";
#endif
}


/**
 *  Initialize the interface.
 *
 *  The base class handles all hardware initialization for us, so we only
 *  have to set up the correct control line states to power the interface.
 */
int T_TX_InterfaceSerPIC::init (SimpleXMLTransfer *config)
{
#if DEBUG_TX_INTERFACE > 0
  std::cout << "T_TX_InterfaceSerPIC::init ()\n";
#endif
  int ret = T_TX_InterfaceSerial::init (config);

  if (ret == 0)
  {
    // initialized successfully, now turn on the power supply for the
    // interface hardware (careful, could throw an exception)
    try
    {
      setRts (true);
      setDtr (false);
    }
    catch (CharDevice::ConfigureDeviceException e)
    {
      setErrMsg ("Setting Rts/Dtr states failed.");
      cerr << "Serial interface initialization: " << getErrMsg () << endl;
      ret = 1;
    }

    int default_sync_byte = DEFAULT_SYNC_BYTE_19200;
    int default_button_channel = DEFAULT_BUTTON_CHANNEL_19200;
    if (T_TX_InterfaceSerial::getBaudRate() == 9600)
    {
      default_sync_byte = DEFAULT_SYNC_BYTE_9600;
      default_button_channel = DEFAULT_BUTTON_CHANNEL_9600;
    }
    // read sync and button byte settings from config file
    SimpleXMLTransfer *port = config->getChild(getXmlChildName() + ".port", true);
    ucSyncByte              = port->attributeAsInt("sync", default_sync_byte);
    iSPIC_ButtonChannel     = port->attributeAsInt("button_channel", default_button_channel);
#if DEBUG_TX_INTERFACE > 0
    std::cout << "  Configured sync byte: 0x" << std::hex << int(ucSyncByte) << std::dec;
    std::cout << ", " << std::string((iSPIC_ButtonChannel == 0) ? "no" : "has") << " button channel";
    std::cout << std::endl;
#endif
  }

  return ret;
}


void T_TX_InterfaceSerPIC::putBackIntoCfg (SimpleXMLTransfer *config)/*{{{*/
{
#if DEBUG_TX_INTERFACE > 0
  std::cout << "T_TX_InterfaceSerPIC::putBackIntoCfg(SimpleXMLTransfer *config)" << std::endl;
#endif

  // Store the port settings
  T_TX_InterfaceSerial::putBackIntoCfg(config);

  // Store additional settings
  SimpleXMLTransfer *port = config->getChild(getXmlChildName() + ".port", true);
  port->setAttributeOverwrite ("sync", ucSyncByte);
  port->setAttributeOverwrite ("button_channel", iSPIC_ButtonChannel);
}


/**
 *  Process the next data byte.
 *
 *  This method processes the next byte that was received on the serial
 *  port.
 *
 *  \param byte   The byte to be processed.
 */
void T_TX_InterfaceSerPIC::processDataByte (unsigned char ucByte)
{
  static int chancount;

  switch (iSyncState)
  {
    default:
    case WAIT_SYNC:
      // detect some sync bytes to make sure the interface
      // sends a valid stream of bytes, not any startup trash
      if (ucByte >= ucSyncByte)
      {
        iSyncCounter++;
      }
      if (iSyncCounter > 4)
      {
        iSyncState = COUNT_CHANNELS;
        iSPIC_NumChannels = 0;
        chancount = 0;
        #if DEBUG_TX_INTERFACE > 0
        printf("T_TX_InterfaceSerPIC: detected sync bytes: 0x%02X\n", ucByte);
        printf("T_TX_InterfaceSerPIC state machine set to COUNT_CHANNELS\n");
        #endif
      }
      break;
      

    case COUNT_CHANNELS:
      iSyncCounter = 0;
      if (ucByte < ucSyncByte)
      {
        chancount++;
      }
      else
      {
        // received next sync byte, end of sync
        iSyncState = IN_SYNC;
        int tmp = (chancount - iSPIC_ButtonChannel);
        #if DEBUG_TX_INTERFACE > 0
        printf("T_TX_InterfaceSerPIC: detected %d channels\n", tmp);
        printf("T_TX_InterfaceSerPIC state machine set to IN_SYNC\n");
        #endif
        if (tmp > TX_MAXAXIS)
        {
          fprintf(stdout, 
                  "T_TX_InterfaceSerPIC: This version of T_TX_InterfaceSerPIC only \n            supports %d channels, will only use 0-7!\n",
                  TX_MAXAXIS);
          tmp = TX_MAXAXIS;
          //~ iSyncState = TRANSMITTER_ERROR;
        }
        iSPIC_NumChannels = tmp;
      }
      break;
      
    case IN_SYNC:
      if (ucByte >= ucSyncByte)
      {
        /* received SYNC byte */
        iSPIC_ChanCount = 0;
      }
      else
      {
        int current_channel = (iSPIC_ChanCount - iSPIC_ButtonChannel);
        if ((current_channel >= 0) && (current_channel < TX_MAXAXIS))
        {
          setRawData(current_channel, (float)((float)ucByte/175.0 - 0.5));
        }
        iSPIC_ChanCount++;
      }
      break;

    case TRANSMITTER_ERROR:
      break;
  }
}


/**
 *  Get the device speed.
 *  
 *  This method overrides T_TX_InterfaceSerial::getDeviceSpeed().
 *  The reason for this is that T_TX_InterfaceSerPIC only supports
 *  two speeds, 9600 baud and 19200 baud. Therefore the GUI only
 *  offers these two values. If the translation from the speed value to the
 *  GUI's combo box item is done in the GUI, the GUI needs to know too much
 *  about this interface, so it's much cleaner to do the translation here.
 */
int T_TX_InterfaceSerPIC::getDeviceSpeed()
{
  int speed;

  // speed as reported by the base class
  int speed_serial = T_TX_InterfaceSerial::getDeviceSpeed();
  
  if (speed_serial == 6)
  {
    // 9600 baud
    speed = 0;
  }
  else
  {
    // 19200 baud, default
    speed = 1;
  }
  return speed;
}

/**
 *  Set the device speed.
 *
 *  This method overrides T_TX_InterfaceSerial::setDeviceSpeed(int).
 *  The reason for this is that T_TX_InterfaceSerPIC only supports
 *  two speeds, 9600 baud and 19200 baud. Therefore the GUI only
 *  offers these two values. If the translation from the speed value to the
 *  GUI's combo box item is done in the GUI, the GUI needs to know too much
 *  about this interface, so it's much cleaner to do the translation here.
 */
void T_TX_InterfaceSerPIC::setDeviceSpeed(int speed)
{
  int speed_serial;
  
  if (speed == 0)
  {
    // 9600 baud
    speed_serial = 6;
    ucSyncByte = 0xF0;
    iSPIC_ButtonChannel = 1;
  }
  else
  {
    // 19200 baud, default
    speed_serial = 7;
    ucSyncByte = 0xFF;
    iSPIC_ButtonChannel = 0;
  }
  T_TX_InterfaceSerial::setDeviceSpeed(speed_serial);
}


I think I got it. I tried to use a switch like in the c++ but each case relies on enum and I couldn’t figure out how to set each enum. I looked deeper into the crrcsim code and it is deep. I concluded that processing makes reading a serial port real easy in comparison. I took one more look at my last attempt in processing and changed i < 7 to i < 8 and now when I print the Channel[] values, they don’t shift in index position.

Now what I am going to try is to use the transmitter to control something in processing, like a joystick but not a joystick.