Go Down

Topic: Serial Comms with RC Receiver Serial data Stream (Read 3411 times) previous topic - next topic

ieee488


I am still concerned that maybe serial1/2/3 or serial for that matter, don't support "non-standard" bauds. If that's the case, I may need to find a custom solution to support 125K.

According to the datasheet and to the person who wrote that other post that I linked, it is suppose to work at 125K, but I haven't personally done it since I use the serial for monitoring data.

You may want to look at SiliconLabs CP2103EK evaluation kit. http://www.silabs.com/products/interface/Pages/CP2103EK.aspx if you are looking for 3rd-party hardware.





-dev

Quote
If this was not what you meant, could you please re-state?
Reading data on the Arduino is different from most PC applications.  Calling Serial.read() does not always return data.  Well, it returns -1 (or 0xFF) if no characters are available.  In a PC app, calling read will usually wait until a data has been received: the PC app is "blocked" until a character comes in.  That's not very noticeable because the PC has a nice multitasking OS so your pointer keeps moving and other windows respond, even though the one app is waiting for a character.

On the Arduino, you can make it block the same way, but there's no OS that will let you do something else while you're waiting for data (yes, there are multitasking solutions, but let's avoid that for now).  So blocking on the Arduino can keep you from doing other things you may need to do, like blinking an LED or catching a button press.

First, lets look at this snippet:

Code: [Select]
  if (Serial1.available() > 0){
    inByte1 = Serial1.read();
    if (inByte1 == 0x3E){
      inBuffer1_16[0] = inByte1;
      if (Serial1.available() > 0)
        inByte1 = Serial1.read();
        if (inByte1 == 0x03){

This starts out great, because it checks to see if any data is available.  If not, it just skips the rest, and loop finishes and gets called again.  If there is data, it reads the first character and saves it.

But then it checks to see if a second character is available.  Guess what... 999 times out of 1000, it won't be there.  At 125000 baud, it takes 80us for each character to arrive.  If you have a 16MHz processor clock, that's ~1200 instructions, or several hundred lines of code in your sketch.  The Arduino is orders of magnitude faster than the data is being sent.

There are a few common ways to write an Arduino sketch that can cope with this speed disparity:

1) Force the Arduino to wait for each character (i.e., block), and write the sketch in a very linear, top-to-bottom fashion:

Code: [Select]
  while (Serial1.available() == 0)
    ; // wait
    inByte1 = Serial1.read();

    if (inByte1 == 0x3E){
      inBuffer1_16[0] = inByte1;

      while (Serial1.available() == 0)
        ; // wait
      inByte1 = Serial1.read();

      if (inByte1 == 0x03){

2) Save each character in a buffer as it arrives until you have received an entire packet.  Then parse the packet all at once with similar linear code:

Code: [Select]
  static uint8_t count = 0; // keeps its value across calls to loop

  if (Serial1.available()) {
    inByte1 = Serial1.read();

    if (inByte1 == 0x3E) {

      // Next packet starting, process the current one we've been saving,
      //   if we have saved enough bytes (skips small/bad packets).

      if ((count > 6) && (inBuffer1_16[0] == 0x3E)) {

        if (inBuffer1_16[1] == 0x03) {
          uint8_t len = inBuffer1_16[2];
          uint8_t id  = inBuffer1_16[3];
          uint8_t typ = inBuffer1_16[4];
          if (typ == 0x31) {
            uint8_t channelDataWords = inBuffer1_16[5];
                ...etc
          }

        } else if (inBuffer1_16[1] == 0x01) {
        }
      }

      // Reset the count to start a new packet with the 0x3E we just received.
      count = 0;
    }

    // Save the current byte
    if (count < sizeof(inBuffer1_16))
      inBuffer1[ count ] = inByte1;
    count++;
  }

3) Parse each character as it arrives, picking up where you left off after doing other things.  This is close to what ieee488 was suggesting:

Code: [Select]
void loop()
{
    // statics keep values across calls to loop
    static uint8_t count         = 0;
    static bool    skipping      = true;
    static uint8_t messageLength = 0;
 
    if (Serial1.available()) { // or 'while'

        uint8_t inByte = Serial1.read();

        if (inByte == 0x3E) {
       
            // Starting a new packet!

            inBuffer1_16[0] = inByte;
            count    = 1;
            skipping = false;

        } else if (!skipping) {

            // Saving a packet, store the current byte

            if (count < sizeof(inBuffer1_16))
              inBuffer1_16[ count ] = inByte;

            // Use the current byte, depending on 'count'
            switch (count) {
              case 1:
                if (inByte != 0x03)
                  skipping = true; // ignore other packet selector values
                break;

              case 2:
                messageLength = inByte;
                // Error check?
                if (messageLength != 40)
                  skipping = true;
                break;

              case 3:
                // packetID = inByte; ?
                inBuffer1_16[3] = inByte;
                break;

              case 4:
                if (inByte != 0x31)
                  skipping = true; // ignore if not channel data
                break;

              case 5:
                channelDataWords = inByte;
                // Error check?
                if (channelDataWords > 32) // != messageLength-8 ?
                  skipping = true;
                break;

              default:
                if (count == messageLength-1) {
                  // All packet bytes received, verify the checksum

                  if (CRC( inBuffer1_16, messageLength-2 ) == ??) {
                    // Packet ok, do something with it (NB: could also
                    //    accumulate CRC as bytes are received)

                    printEXPacket(); // ???
                  }
                 
                  // Wait for the next packet to start...
                  skipping = true;
                }
                break;

            }

            count++;
        }
    }

    // Do other things, too?
}

Choice 1 is sometimes easier for newbies to understand, as it is very linear.  The disadvantage is that it blocks and does not recover from data errors very well.

Choice 2 is pretty easy to understand and it doesn't block; other tasks can do things while waiting for a complete packet.  The disadvantage is that all the packet processing happens when the last byte is received (actually, the first byte of the next packet in that code).  This is similar to the Serial Input Basics example sketches.

Choice 3 is more difficult to understand, because the entire section is executed for each character, either starting a new packet or using the switch statement to handle each char.  Different cases do a little work, depending on the count.  The advantage is that the processing is spread out over each character, and you can decide to skip the rest of a packet at any point.  This is actually a Finite-State machine approach, and it's worth searching here for recent posts on FSMs (my posts here and here).  Nick Gammon has a great description, too.

Cheers,
/dev
Really, I used to be /dev.  :(

TonyEll

/dev, I got it. My previous comments on the packet ID being the same for many packets received gave me a clear sense that timing was the key and that is what you clearly spelled out. My lack of experience simply didn't get me to what youve just spelled out. I am a newbie at this (Arduino) and have never done serial comms before but your explanation is clear, concise with a variety of pointers. It is very much appreciated and I'll report back once I've worked through this. Thank you for spending the time on your extensive reply.

TonyEll

Hi all, so firstly, thanks to /dev for his FSM - this is working a treat with the EX Bus serial stream. I've integrated the CRC as well as the I2C for the PWM drivers and the system is working extremely well.
From my perspective, this inquiry is resolved - thanks again /dev!! FSM's will be something I will use greatly in the future!!

TonyEll

Hi all again, I'm looking for some more expert guidance on the project noted in this thread. I've worked to get this FSM fully functional with the data packets I'm interested in using for servo deflection data (per the original EX Bus protocol attachment) and have a configuration that's now running 80+ servo's.

The Jeti systems allows the connection of TWO RC receivers to a slave device via a configuration called "Dual Path". In this configuration both receivers are enabled to transmit the EX data to the slave device (the device I'm designing is the slave device) - it's a redundancy feature in case one receiver looses connection with the transmitter.

So, I need to switch between two Mega serial ports (Serial1 and Serial2), IF one receiver has missing frames. I've attached a picture of what the output from each receiver looks like with timings. I can live with up to two frames missing from Serial1 before needing to switch to Serial2 and vice versa.

As I indicated previously, my serial communications knowledge is very minimal. I've attached my state machine loop (thanks /dev), with a modification to the front end designed to switch between the two serial streams IF Serial1 or Serial2 do not receive valid data after exTimeOut has expired (set to zero after the CRC check of the EX data stream ) - the timeout is currently set at 45ms. The code does not work and I don't have the experience to determine why this is the case. I suspect I have oversimplified it and need more validation somewhere or I've simply misinterpreted what Serial.available() means.

Note that the specs for the EX data stream specifically notes that NO EX data packet is sent to the slave device IF the master (receiver) did not get it's data from the transmitter - hence my assumption that Serialx.available will be 0.

Can anyone provide guidance please?

Code: [Select]

void loop()
{
  // statics keep values across calls to loop
 
  static uint8_t count            = 0;
  static uint8_t msgLength        = 0;
  static uint8_t chDataBytes      = 0;
  static uint8_t errorCount       = 0;
  static bool    skipping         = true;
  static bool    servoWrite       = false;
  static bool    is24Ch           = false;
  uint8_t        inByte;

  if (Serial1.available() == 0 && timeElapsed > exTimeOut){
    inPort = 1;           // Goto Serial2
  }
  else if (Serial2.available() == 0 && timeElapsed > exTimeOut){
    inPort = 0;           // Go back to Serial1
  }
  if (inPort == 0){
    if (Serial1.available()) {
      inByte = Serial1.read();
    }
    else if (inPort == 1){
      if (Serial2.available()) {
        inByte = Serial2.read();
      }
    }
    if (inByte == chHeader) {           // 0x3E - Starting a new packet!
      inBuffer[0] = inByte;
      count    = 1;
      skipping = false;                 // Flag that 0x3E found
    }
    else if (!skipping) {               // handles all after 0x3E
      if (count < sizeof(inBuffer))
        inBuffer[ count ] = inByte;

      switch (count) {            // Use current byte based on 'count'
        case 1:
          if (inByte != listenOnly){     // Check for 0x03 and 0x01 (use both)
            if (inByte != slaveResp)
            skipping = true;
          }
          break;

        case 2:
          msgLength = inByte;
          if (msgLength != len16Ch){     // Check message length byte
            if (msgLength != len24Ch){
              skipping = true;
            }
            else if (msgLength == len24Ch){
              is24Ch = true;
            }
          }
          break;

        case 3:
          inBuffer[3] = inByte;         // Packet ID - store for CRC
          break;

        case 4:
          if (inByte != chData)         // Check for 0x31
            skipping = true;            // if not correct, restart
          break;

        case 5:
          chDataBytes = inByte;
            if (chDataBytes != chBytes16){ // check for 0x20 (32) bytes
              if (chDataBytes != chBytes24 && is24Ch){// check 0x28 (48)
                skipping = true;            // if not correct, restart
              }
            }
          break;
         
        default:
          if (count == msgLength-1) {     // All packet bytes received - CRC check next
            uint8_t crcMSB, crcLSB;
            if (msgLength == len16Ch){    // 16 channel CRC variable assignment
              crcMSB = crcMSB16; crcLSB = crcLSB16;
            }
            else if (msgLength == len24Ch){// 24 channel CRC variable assignment
              crcMSB = crcMSB24; crcLSB = crcLSB24;
            }
            // Convert 2 x CRC bytes into a single word
            uint16_t crc16z = inBuffer[crcMSB] * 256 + inBuffer[crcLSB];
            if (get_crc16z(inBuffer, msgLength-2) == crc16z) {
              timeElapsed = 0;             // Start frame timer at end of current frame
              byteToWordToTick(msgLength); // CRC Good, convert data to words & ticks
              servoWrite=true;             // Ready to write servo
             
              }
            skipping = true;               // This is the reset to wait next packet
          }
          break;
      }
      count++;
    }
  }

  if (servoWrite){
    if (loopPon){
      loopPon = false;
      if (msgLength == len16Ch){
        init16(msgLength);
      }
      else if (msgLength == len24Ch){
        init24(msgLength);
      }
    }
    if (msgLength == len16Ch){
      write16(msgLength);
    }
    else if (msgLength == len24Ch){
      write24(msgLength);
    }
    servoWrite=false;
  }
}

gpsmikey

One thought here that may or may not be applicable here - seems to me most of the (at least the smaller) arduino stuff uses a resonator instead of a crystal for the clock.  Those resonators have a much bigger tolerance than the crystals do, so if the one you are using is using a resonator (which is what the baud rate is being derived from) that may be an issue at the higher baud rates (in my experience if you have 2 devices connected and each one has a +/- error of 2%, the total error will always turn out to be 4% - murphy has a way with this  :o  )  It may also be an issue the transmitter is on the edge too.
mikey
-- you can't have too many gadgets or too much disk space !
old engineering saying: 1+1 = 3 for sufficiently large values of 1 or small values of 3

-dev

Image embedded for our convenience:



(Technique described here.)
Really, I used to be /dev.  :(

-dev

Quote
So, I need to switch between two Mega serial ports (Serial1 and Serial2), IF one receiver has missing frames...  I've attached my state machine loop, with a modification to the front end designed to switch between the two serial streams IF Serial1 or Serial2 do not receive valid data after exTimeOut has expired
If I understand what you're saying, it sounds like there are 3 state machines here: one for Serial1 frames, one for Serial2 frames, and one for switching between Serial1 and Serial2.

As you have it now, the FSM variables are shared between the two receivers.  This is bad, because any character received one on side will affect the other side.  You need two sets of variables that are independently driven by the two serial ports.  A good way to group these variables is with a C++ "class".

First, I would suggest modifying the original one-serial port sketch so that it uses a class instance to hold on to the frame state machine variables.  Instead of statics in loop, these variables will be grouped into a new class.  This code:

Code: [Select]
    // statics keep values across calls to loop
    static uint8_t count         = 0;
    static bool    skipping      = true;
    static uint8_t messageLength = 0;

... will turn into this code:

Code: [Select]
class EXFSM_t // or whatever type name you want
{
public:
  // Here are data members of this class.  Initial values do not go here!
  uint8_t  count;
  bool     skipping;
  uint8_t  messageLength;
  uint8_t  chDataBytes
  uint8_t  errorCount;
  bool     is24chan;
  uint32_t lastFrameReceived; // a timestamp
  bool     servoWrite;  // ?

  // Storage for a packet
  static const uint8_t BUFFER_SIZE = 40;
  uint8_t  inBuffer[ BUFFER_SIZE ];   

  // This is the constructor.  It initializes new instances of this class
  EXFSM_t()
  {
     // Since this "routine" is short, I'll just put it here.

    count         = 0;
    skipping      = true;
    messageLength = 0;
    chDataBytes = 0;
    errorCount   = 0;
    is24chan = false;
    lastFrameReceived = 0;
    servoWrite = false;
  };

  //  This member function should be called to handle one received character
  void process( uint8_t inByte )
  {
     // This is taken from your loop code

     if (inByte == chHeader) {           // 0x3E - Starting a new packet!
      inBuffer[0] = inByte;
      count    = 1;
      skipping = false;                 // Flag that 0x3E found
    }
    else if (!skipping) {               // handles all after 0x3E
      if (count < sizeof(inBuffer))
        inBuffer[ count ] = inByte;

      switch (count) {            // Use current byte based on 'count'
        case 1:
          if (inByte != listenOnly){     // Check for 0x03 and 0x01 (use both)
            if (inByte != slaveResp)
            skipping = true;
          }
          break;

<snip>
         
        default:
          if (count == msgLength-1) {     // All packet bytes received - CRC check next
            uint8_t crcMSB, crcLSB;
            if (msgLength == len16Ch){    // 16 channel CRC variable assignment
              crcMSB = crcMSB16; crcLSB = crcLSB16;
            }
            else if (msgLength == len24Ch){// 24 channel CRC variable assignment
              crcMSB = crcMSB24; crcLSB = crcLSB24;
            }
            // Convert 2 x CRC bytes into a single word
            uint16_t crc16z = inBuffer[crcMSB] * 256 + inBuffer[crcLSB];
            if (get_crc16z(inBuffer, msgLength-2) == crc16z) {
              lastFrameReceived = millis();             // Start frame timer at end of current frame
              byteToWordToTick(msgLength); // CRC Good, convert data to words & ticks
              servoWrite=true;             // Ready to write servo
             
              }
            skipping = true;               // This is the reset to wait next packet
          }
          break;
      }
      count++;
    }
  }; // end of process(inByte)

}; // end of class EXFSM_t

//----------------
// Ok, now declare two instances (i.e., variables) of that class:

EXFSM_t receiver1, recevier2; //  constructor initializes two state machines

//  And here's your loop, but most of the code has been moved into the class above.
//     (Using only one instance at first)

void loop()
{
  // Feed one state machine
  if (Serial1.available()) {
    receiver1.process( Serial1.read() );

    if (receiver1.servoWrite) {

      if (msgLength == len16Ch){
        init16(msgLength);
        write16(msgLength);
      }
      else if (msgLength == len24Ch){
        init24(msgLength);
        write24(msgLength);
      }
    }

    receiver1.servoWrite=false; // clear for next time
  }
}

Get this working for your original one-receiver setup.  When that works, it's easy to add a second instance to loop:

Code: [Select]
  // Feed the second state machine
  if (Serial2.available()) {
    receiver2.process( Serial2.read() );

    if (receiver2.servoWrite) {

      if (msgLength == len16Ch){
        init16(msgLength);
        write16(msgLength);
      }
      else if (msgLength == len24Ch){
        init24(msgLength);
        write24(msgLength);
      }
    }

    receiver2.servoWrite=false; // clear for next time
  }

It may also be easier to see how to implement the master coordination, using these two instances and their (separate) states.  If not, post whatever you end up with and ask some more questions.

Cheers,
/dev
Really, I used to be /dev.  :(

TonyEll

Hello again /dev (Devin??).

Thanks again - your guidance is really appreciated.

A class eh? I've only read about them, never created one! I do understand the concept of a class though, I use it for servo's so let me look at this a little closer. Once again, thanks for the pointer!!

Tony

TonyEll

If you were reading my later posts, apologies. I deleted them as I felt I was over-posting and not doing my homework. I have compiled the new version of code now and I'll test it tomorrow. Won't be able to tonight.

TonyEll

Hi all,

Once again, thanks /dev!!

That got it all!! Both Serial signals being used in steady state, Rx1 used when no RX2 and vice versa!! This kinda happened by default which was good.

Since it's now set up to naturally switch between the two receivers by default, monitoring for a missing frame is no longer needed. If one servo doesn't transmit the EX data, the software simply uses the other one.

The only case left is when both disappear but that's a fail safe scenario and that will need afull programming feature which is my next step in this project.


/dev, thanks. Two instances of the FSM was a perfect solution.


Tony

-dev

Quote
Both Serial signals being used in steady state, Rx1 used when no RX2 and vice versa!! This kinda happened by default which was good.
It sounds like if both are received, the write16/24 happens twice, but that's ok.  Simple is good.

If you do need to filter the second packet, perhaps there is a sequence number in the packet.  At a minimum, you could use the lastFrameReceived timestamp.

Quote
doing my homework
This is greatly appreciated.  Too often, our replies are not read carefully and taken seriously.  It's a pleasure to help when you put in some effort!

Cheers,
/dev
Really, I used to be /dev.  :(

TonyEll

Hi all yet again,

I was wondering if one or more of you could look over the following and hint as to what I am doing wrong. I've vacillated over this for a couple days now with no luck.

My bottom line is that I have too much information to process and then send over I2C to peripheral chips attached to the 2560. My window is 14ms max and I'm currently taking 18ms to process everything I need to. Part of the reason is that I send an I2C packet with stop for every servo being updated. I will be changing that to a single packet for all servos.

Additionally, I process the CRC, byte to word conversion and then conversion to "offTicks (a requirement for the peripheral chips), at the end of processing the EX bus data stream. I'd like to speed that process up by updating the CRC, bytes to words and words to offTicks when each word of data comes in over the EX Bus. As such, I've pulled in the old CRC functions into the EXSFM class (thanks /dev). and am trying to get it to work but I'm missing something. I've done the same for the byte to word and word to offtick calculators.

I'm sure you software experts can see exactly where I've gone wrong but I can't. Any pointers?

I've also included the previous version of CRC calulator and bytetowordtotick calculator in the next post.

Code: [Select]
class EXFSM_t
{
  public:
 
    uint16_t count;                       // Counter for EX FSM
    uint16_t ret_val;                     // Returned CRC value after each byte
    uint16_t crc16z;                      // Result of CRC calculation
    uint8_t  msgLength;                   // Length of overall packet
    uint8_t  chDataBytes;                 // Number of channel specific bytes
    uint8_t  crcMSB;                      // location of LSB CRC byte in array
    uint8_t  crcLSB;                      // location of LSB CRC byte in array
    bool     skipping;                    // FSM state escape
    bool     servoWrite;                  // CRC valid, write servos
    bool     is24Ch;                      // Data is for 24 channels

    static const uint8_t  PK_BUFFER_SIZE = 0x38; // Storage for a packet
    static const uint8_t  CH_BUFFER_SIZE = 0x28; // Word buffer for channel data

    uint8_t  inBuffer[ PK_BUFFER_SIZE ];        // EX Data Stream Buffer (bytes)
    uint16_t chPwm[ CH_BUFFER_SIZE ];           // Word buffer for channel data
    uint16_t servoOff[ CH_BUFFER_SIZE ];        // Servo off tick array

    EXFSM_t()
    {
      ret_val           = 0;
      count             = 0;
      skipping          = true;
      msgLength         = 0;
      chDataBytes       = 0;
      is24Ch            = false;
      servoWrite        = false;
      crc16z            = 0;
      crcMSB            = crcMSB16;  // pre-assign, change later if needed
      crcLSB            = crcLSB16;  // pre-assign, change later if needed
    };

    //  This member function should be called to handle one received byte
    void process( uint8_t inByte )
    {
      if (inByte == chHeader) {           // 0x3E - Starting a new packet!
        inBuffer[0] = inByte;
        ret_val = get_crc16z(inBuffer, 0);// Load first byte into CRC engine count=0
        count    = 1;
        skipping = false;                 // Flag that 0x3E found
       
      }
      else if (!skipping) {               // handles all after 0x3E
        if (count < sizeof(inBuffer))
          inBuffer[ count ] = inByte;

        switch (count) {                  // Use current byte based on 'count'
          case 1:
            if (inByte != listenOnly) {   // Check for 0x03 and 0x01 (both)
              if (inByte != slaveResp) {
                skipping = true;
              }
            }
            ret_val = get_crc16z(inBuffer, ret_val);   // Load 2nd byte into CRC engine count=1
            break;

          case 2:
            msgLength = inByte;
            if (msgLength != len16Ch) {   // Check message length byte
              if (msgLength != len24Ch) {
                skipping = true;          // if not correct, restart
              }
              else if (msgLength == len24Ch) {
                is24Ch = true;
              }
            }
            ret_val = get_crc16z(inBuffer, ret_val);   // Load 3rd byte into CRC engine
            break;

          case 3:
            inBuffer[3] = inByte;         // Packet ID - store for CRC
            ret_val = get_crc16z(inBuffer, ret_val);   // Load 4th byte into CRC engine
            break;

          case 4:
            if (inByte != chData) {       // Check for 0x31
              skipping = true;
            }
            ret_val = get_crc16z(inBuffer, ret_val);   // Load 5th byte into CRC engine
            break;

          case 5:
            chDataBytes = inByte;
            if (chDataBytes != chBytes16) { // check for 0x20 (32) bytes
              if (chDataBytes != chBytes24 && is24Ch) { // check 0x28 (48)
                skipping = true;
              }
            }
            ret_val = get_crc16z(inBuffer, ret_val);   // Load 6th byte into CRC engine
            break;
           
          default:
           
            if (count == msgLength - 1) { // All packet bytes received count = 55
              if (msgLength == len24Ch) { // 24 channel CRC variable
                crcMSB = crcMSB24; crcLSB = crcLSB24;
              }
              // Convert 2 x CRC bytes into a single word
              crc16z = inBuffer[crcMSB] * 256 + inBuffer[crcLSB];
             
              if (ret_val == crc16z) {
                servoWrite = true;         // Ready to write servo
              }
              else {
                Serial.println("CRC BAD");
              }
              skipping = true;              //Reset to wait next packet
            }
           
            if (count < 54){
              ret_val = get_crc16z(inBuffer, ret_val);    // Load nth byte into CRC engine
            }
//            if (count % 2){               // detects ODD occurance of count, first val=7
//              byteToWord(count);          // converts two bytes to a word, loads to chPwm
//              wordToTicks(count);         // converts chPwm[] to servoOff[]
//            }
            break;
        }
        count++;
      }
    };

    // CRC16 value manager for the EX data Stream
    uint16_t get_crc16z(uint8_t *p, uint16_t crc16_data)
    {
      crc16_data = crc16_calc(crc16_data, p[0]);
         p++;
      return (crc16_data);
    };

    // CRC16 calculator for the EX data Stream
    uint16_t crc16_calc(uint16_t crc, uint8_t data)
    {
      uint16_t ret_val;
      data ^= (uint8_t)(crc) & (uint8_t)(0xFF);
      data ^= data << 4;
      ret_val = ((((uint16_t)data << 8 ) | ((crc & 0xFF00) >> 8))
                 ^ (uint8_t)(data >> 4)
                 ^ ((uint16_t)data << 3));
      return ret_val;
    };

    // Converts two EX Stream bytes to a single 16 bit word

    void byteToWord(uint8_t count){
      uint8_t arrayCount = map(count, 6, 55, 0, 23);
      chPwm[arrayCount] = inBuffer[count] * 256 + inBuffer[count-1];
    };

    // Converts each servo deflection word to PCA9685 ticks

    void wordToTicks(uint8_t count){
      uint8_t arrayCount = map(count, 6, 55, 0, 23);
      servoOff[arrayCount] = (((chPwm[arrayCount] / 8) - servoMin_us) / (servoDelta_us))
                           * (maxTicks_tic60) + servoMin_tic60 + servoOnTick;
    };
};                                          // end of class




TonyEll

#28
Mar 28, 2016, 03:18 pm Last Edit: Mar 28, 2016, 03:42 pm by TonyEll
The older versions of the calculators

Code: [Select]
void byteToWordToTick(uint8_t msgLength, uint8_t rxSource)
{
  uint8_t channelCnt = count16Ch;             // Assume 16 channel (count 30)

  if (rxSource == 1) {                        // Data stream from RX1
    if (rx1.msgLength == len24Ch) {
      channelCnt = count24Ch;                 // switch if 24 channel detected (count 46)
    }
    uint8_t arrayCount = 0;
    for (uint8_t i = 0; i <= channelCnt; i = i + 2) { // word based counter, not bytes, see notes
      chPwm[arrayCount] = rx1.inBuffer[servo1MSB + i] * 256 + rx1.inBuffer[servo1LSB + i];
      arrayCount++;
    }
  }

  else if (rxSource == 2) {                   // Data stream from RX2
    if (rx2.msgLength == len24Ch) {
      channelCnt = count24Ch;                 // switch if 24 channel detected (count 46)
    }
    uint8_t arrayCount = 0;
    for (uint8_t i = 0; i <= channelCnt; i = i + 2) { // word based counter, not bytes, see notes
      chPwm[arrayCount] = rx2.inBuffer[servo1MSB + i] * 256 + rx2.inBuffer[servo1LSB + i];
      arrayCount++;
    }
  }

  uint8_t arrayCount = 0;
  for (uint8_t i = 0; i <= channelCnt; i = i + 2) { // word based counter, not bytes, see notes

    servoOff[arrayCount] = (((chPwm[arrayCount] / 8) - servoMin_us) / (servoDelta_us))
                           * (maxTicks_tic60) + servoMin_tic60 + servoOnTick;
    arrayCount++;
  }
  return;
}



/* --------- CRC DECODER -----------*/

uint16_t crc16_calc(uint16_t crc, uint8_t data)
{
  uint16_t ret_val;
  data ^= (uint8_t)(crc) & (uint8_t)(0xFF);
  data ^= data << 4;
  ret_val = ((((uint16_t)data << 8 ) | ((crc & 0xFF00) >> 8))
             ^ (uint8_t)(data >> 4)
             ^ ((uint16_t)data << 3));
  return ret_val;
}

uint16_t get_crc16z(uint8_t *p, uint16_t len)
{
  uint16_t crc16_data=0;
  while(len--)
  {
     crc16_data = crc16_calc(crc16_data, p[0]);
     p++;
  }
  return (crc16_data);
}

TonyEll

So, after some more significant reading, I have learned that the variable inBuffer is an address, which I did not know!!. So sending inBuffer appears to reset the CRC calculators to the wrong value. I have modified the calls to send the actual data from the buffer (inBuffer[count]) and removed the pointer references in get_crc16z. Also updated the count selector in default: to msgLength-3. Still no joy.

So, my new calls are:
Code: [Select]
ret_val = get_crc16z(inBuffer[count], ret_val)

My new crc calc is

Code: [Select]
    // CRC16 value manager for the EX data Stream
    uint16_t get_crc16z(uint8_t data, uint16_t crc16_data)
    {
      crc16_data = crc16_calc(crc16_data, data);
      return (crc16_data);
    };

    // CRC16 calculator for the EX data Stream
    uint16_t crc16_calc(uint16_t crc, uint8_t data)
    {
      uint16_t ret_val;
      data ^= (uint8_t)(crc) & (uint8_t)(0xFF);
      data ^= data << 4;
      ret_val = ((((uint16_t)data << 8 ) | ((crc & 0xFF00) >> 8))
                 ^ (uint8_t)(data >> 4)
                 ^ ((uint16_t)data << 3));
      return ret_val;
    };

Go Up