Pelco D - Sending and receiving byte arrays over serial

[EDIT]
When I made my first post I thought this would be a quick question about byte arrays over serial.
Then it got a bit more elaborate/complicated :slight_smile:

I changed the topic title to reflect the topic at hand.

I am/was working on:
-- receiving pelco D messages over serial
-- Controlling 2 motors over serial

Board of choice: Arduino Nano Every.
With some tweaks you have a serial USB for debugging an three hardware serial ports available.

At the time of this edit (2023.02.26) I have learned quite a bit on/from this topic.
[/EDIT]

Hi all,

I'm working on a serial project and I have a question about sending out a byte array.

My function is this one:

void transmit(){

  //                  strt  adrs  cmd1  cmd2  data1 data2 chk
  byte msg_left[7]  ={0xFF, 0x01, 0x00, 0x04, 0xBF, 0x00, 0x00};
  byte msg_right[7] ={0xFF, 0x01, 0x00, 0x02, 0xBF, 0x00, 0x00};
  byte msg_up[7]    ={0xFF, 0x01, 0x00, 0x08, 0x00, 0xAF, 0x00};
  byte msg_down[7]  ={0xFF, 0x01, 0x00, 0x10, 0x00, 0xAF, 0x00};

  commands[4] = {msg_left, msg_right, msg_up, msg_down};
  
  Serial1.write(commands[0], 7);
      
} // End void transmit

This gives an error: " no matching function for call to 'write(byte&, int)' "

How can I declare an array that contains my bytes arrays?

Thanks for reading!

 Serial1.write(commands[0], 7);

did you mean

 Serial1.write(commands[0][6]);

Apart from the missing square brackets on the column index note that there is no level 7, only 0 to 6

Problems with the write() function aside, the variable 'commands' has no datatype:

  commands[4] = {msg_left, msg_right, msg_up, msg_down};

Hello

byte * commands[4] = {msg_left, msg_right, msg_up, msg_down};

sorry
commands needs to be defined as an array of byte ptrs

  byte *commands[4] = {msg_left, msg_right, msg_up, msg_down};
1 Like

Ok, thanks!

It works as intended now:

On the TX side:

void transmit(){

  //                  strt  adrs  cmd1  cmd2  data1 data2 chk
  byte msg_right[7] ={0xFF, 0x01, 0x00, 0x02, 0x3F, 0x00, 0x00};
  byte msg_left[7]  ={0xFF, 0x01, 0x00, 0x04, 0x3F, 0x00, 0x00};
  byte msg_up[7]    ={0xFF, 0x01, 0x00, 0x08, 0x00, 0x3F, 0x00};
  byte msg_down[7]  ={0xFF, 0x01, 0x00, 0x10, 0x00, 0x3F, 0x00};

  byte *commands[4] = {msg_right, msg_left, msg_up, msg_down};

  for(int i=0;i<4;i++) Serial1.write(commands[i], 7);
}

On the RX side:

void loop() {

  if (Serial3.available() >= 7)
  {
    if(Serial3.read() == 255)
    {
      static int msg[6];                                                // Store received bytes in an array as integers

      for(int i=0; i<=6;i++) msg[i] = Serial3.read();                   // After startbyte, read remaining 6 bytes as commands
      
      Serial << msg[0] << " : " << msg[1] << " : " << msg[2] << " : " << msg[3] << " : " << msg[4] << " : " << msg[5] << endl;
    }
  }
} // end loop

Output:

1 : 0 : 2 : 63 : 0 : 0
1 : 0 : 4 : 63 : 0 : 0
1 : 0 : 8 : 0 : 63 : 0
1 : 0 : 16 : 0 : 63 : 0

You are reading 7 elements into a 6-element array. It should be:
for(int i=0; i<6; i++) msg[i] = Serial3.read();
Going off the end of the array can damage data.

Thanks for pointing that out, I see the problem.

It hasn't been causing problems because I'm just testing for now. At the time of reading there was no 7th byte present in the serial buffer I guess?

As soon as I added a delay in the receiving loop the problems started. That would be a topic of it's own I think.

No, keep your discussion here, per forum guidance we should keep all discussions about any given project within a thread, otherwise you end up explaining it all all over again in another thread. Context is important.

what happens of you're received gets out of sync, doesn't see the 1st byte of the msg or the msg length varies?

@camsysca : You'r probably right but the topictitle doesn't really cover the content anymore by now. But I'll keep it here. EDIT: I could still change the topic title, so ok.

Some more info on what I'm working on:
-- I'm trying to read in Pelco-D messages/commands
-- I'm only interested in pan (left, right) and tilt (up, down)

I assume the hardware sending the commands keeps sending a stream of messages as long as an action is required.

For example:

  //                  strt  adrs  cmd1  cmd2  data1 data2 chk
  byte msg_left[7]  ={0xFF, 0x01, 0x00, 0x04, 0xBF, 0x00, 0x00};

address: 01
command: 0x04 go left
speed: 0xBF

So as long as the camera should be moving left at speed 0xBF the same 7 bytes message keeps coming in. Loosing a message and re-syncing on 0xFF should not be a problem. And the message length is always 7 bytes as defined in the protocol.

An update on the code:

TX:

void transmit(){

/*
  //                  strt  adrs  cmd1  cmd2  data1 data2 chk
  byte msg_right[7] ={0xFF, 0x01, 0x00, 0x02, 0x3F, 0x00, 0x00};
  byte msg_left[7]  ={0xFF, 0x01, 0x00, 0x04, 0x3F, 0x00, 0x00};
  byte msg_up[7]    ={0xFF, 0x01, 0x00, 0x08, 0x00, 0x3F, 0x00};
  byte msg_down[7]  ={0xFF, 0x01, 0x00, 0x10, 0x00, 0x3F, 0x00};

  byte *commands[4] = {msg_right, msg_left, msg_up, msg_down};

  for(int i=0;i<4;i++) Serial1.write(commands[i], 7);
*/

// =========================================================================
// TESTCODE

  byte commands[][7] = 
  {
    {0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05},
    {0xFF, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15},
    {0xFF, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25},
    {0xFF, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35},
    {0xFF, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45},
    {0xFF, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55},
    {0xFF, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65},
    {0xFF, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75},
    {0xFF, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85},
    {0xFF, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95},
    {0xFF, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5},
    {0xFF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5},
    {0xFF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5},
    {0xFF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5},
    {0xFF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5},
    {0xFF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5},
    {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}      // Invalid command, contains only start bytes
  };

  for(int i=0;i<17;i++) Serial1.write(commands[i], 7);

// =========================================================================
// END TESTCODE */
      
} // End void transmit

RX:

void loop() {

  //Serial << "Nr of bytes in input buffer: " << Serial3.available() << endl;
  
  if(Serial3.available()==63) flush_buffer();                           // If the input buffer is full, clear it for new messages
  
  if (Serial3.available() >= 7)                                         // Check if there are at least 7 bytes in the serial buffer, 7 bytes is the length of a pelco-d message
  {
    if(Serial3.read() == 255)                                           // If the start byte of a message is found
    {
      static byte msg[6];                                               // Create an integer array to store the remaining 6 command bytes of the message

      Serial3.readBytes(msg, 6);                                        // Read remaining 6 bytes from serial buffer and store into array
      
      Serial << msg[0] << ":" << msg[1] << ":" << msg[2] << ":" << msg[3] << ":" << msg[4] << ":" << msg[5] << endl;
    }
  }

  //delay(20);         // Delay to test what happens if the serial input buffer fills up
  
} // end loop


void flush_buffer(){
  Serial << "flushing" << endl;
  while(Serial3.available()) Serial3.read();
  delay(10);                                                            // Short delay to receive new messages
}

As thread author, I believe you can modify the title and/or change the category. See the pencil icon beside the thread title at the top of the thread page.

Are you sure cmd, data or checksum bytes cannot be 0xFF ?

Yes.

Unless the checksum calculates to 0xFF, but I'm pretty sure Pelco has that covered.

If you want something more robust, look at this : t1024219.ino - Wokwi Arduino and ESP32 Simulator

It was originally made for a similar topic : How to Arduino read the HEXA data stream

Thanks,

Based on this I now have a new function that:
-- takes one byte
-- stores it in an array
-- If the byte is 0xFF, the storing index is set to zero
-- Returns 'true' if the message in the array is complete and valid

It works but please do let me know if you see a problem I'm not aware of.

Complete test code and output:

#include <Streaming.h> 

const uint8_t  num_databytes = 6;                            // Number of data bytes in Pelco message
uint8_t new_msg[num_databytes];                              // Message array to store incoming bytes   


void setup()
{
  Serial.begin( 115200 );
}


bool process_byte( uint8_t recv_byte )
{
  static uint8_t index = 0;                                  // Index to place received bytes at
  
  if (recv_byte == 0xFF)
  {
    index = 0;                                               // A startbyte has been received, set index to zero
    return false;                                            // Return false for 'new message ready'
  }
  else
  {
    new_msg[index] = recv_byte;                              // Place receieved byte into message array
    
    if ( ++index == num_databytes )                          // After placing a byte at the last message array index,
    {                                                           
      index = 0;                                             // reset the index to prevent 'out of index' error 

      /* Valid data messages (without startbyte 0xFF):
              adrs  cmd1  cmd2  dta1  dta2  chk  
      msg     [0]   [1]   [2]   [3]   [4]   [5]    
      right:  0x01, 0x00, 0x02, 0x3F, 0x00, 0x00     
      left:   0x01, 0x00, 0x04, 0x3F, 0x00, 0x00     
      up:     0x01, 0x00, 0x08, 0x00, 0x3F, 0x00     
      down:   0x01, 0x00, 0x10, 0x00, 0x3F, 0x00      
      */
      
      int cmd2 = new_msg[2];                                 // Check if the message store in new_msg[] is valid
      int dta1 = new_msg[3];                                 // If so, return true for 'new message ready'
      int dta2 = new_msg[4];
      
      if( (cmd2==2||cmd2==4)  && dta2==0 || 
          (cmd2==8||cmd2==16) && dta1==0 ) return true;
      
    }
    return false;                                           // A byte has been stored in new_msg[] but 
  }                                                         // new_msg[] is not yet complete and checked
}


void simulate()
{
  const uint8_t sensor_bytes[] =                            // simulate receiving bytes, 4 valid sequences in data
  {
                      0x0A, 0x0B, 0x0C, 0x0D,       
    0xFF, 0x01, 0x00, 0x02, 0x3F, 0x00, 0x00,               // valid sequence right 1:0:2:63:0:0
    0xFF, 0x01, 0x00, 0x04, 0x3F, 0x00, 0x00,               // valid sequence left  1:0:4:63:0:0
    0xFF, 0x10, 0x00,
    0x04, 0x3F, 0x00,0xFF,
    0xFF, 0x01, 0x00, 0x08, 0x00, 0x3F, 0x00,               // valid sequence up    1:0:8:0:63:0
    0x00, 0x08, 0x5A, 0x3F, 0xA5,
    0xFF,
    0x08, 0x80, 0x3F, 0x41, 0x80, 0x3F, 0xD2,
    0x5A, 0x3F, 0xA5,
    0xFF, 0x01, 0x00, 0x10, 0x00, 0x3F, 0x00,               // valid sequence down  1:0:16:0:63:0
    0xFF, 0x01, 0x07, 0x08, 0x09,                   
  };

  const size_t sensor_bytes_size = sizeof( sensor_bytes ) / sizeof( sensor_bytes[0] );

  for ( uint8_t i = 0; i < sensor_bytes_size; ++i )
  {
    if(process_byte(sensor_bytes[i]))                                           // If process_byte() returns true that means a new  
    {                                                                           // valid message is ready to be read from new_msg[]
      for(int i=0;i<=num_databytes-2;i++) Serial << new_msg[i] << ":";          // Print the new message to serial monitor
      Serial << new_msg[num_databytes-1] << endl;
    }
  }
}


void loop()
{
  Serial << "=============" << endl;
  simulate();
  delay(1000);
}

Output:

1:0:2:63:0:0
1:0:4:63:0:0
1:0:8:0:63:0
1:0:16:0:63:0
=================================================
1:0:2:63:0:0
1:0:4:63:0:0
1:0:8:0:63:0
1:0:16:0:63:0
=================================================

i think it's great that you tested the code with simulated input, but it would be better if you structured the code so that the exact same routines can be used in the final code.

also print the bad cases to see if all scenarios are covered.

and accept all valid msgs, not just the one you're interested in and validate the checksum to reject

consider this approach

            /* Valid data messages (without startbyte 0xFF):
            adrs  cmd1  cmd2  dta1  dta2  chk
            msg     [0]   [1]   [2]   [3]   [4]   [5]
            right:  0x01, 0x00, 0x02, 0x3F, 0x00, 0x00
            left:   0x01, 0x00, 0x04, 0x3F, 0x00, 0x00
            up:     0x01, 0x00, 0x08, 0x00, 0x3F, 0x00
            down:   0x01, 0x00, 0x10, 0x00, 0x3F, 0x00
            */

char s [90];

// -----------------------------------------------------------------------------
const uint8_t MsgLen = 7;
      uint8_t msg [MsgLen];
bool
process_msg (void)
{
    byte chksum = 0;
    Serial.print (" process_msg: ");
    for (int i = 0; i < MsgLen; i++)  {
        sprintf (s, " 0x%02x", msg [i]);
        Serial.print (s); 
        chksum ^= msg [i];
    }

    sprintf (s, " - 0x%02x %s", chksum, chksum ? "*" : " ");
    Serial.println (s); 

    return ! chksum;
}

// -------------------------------------
void
process_byte (
    uint8_t recv_byte )
{
    static uint8_t index = 0;

    if (0 == index && recv_byte == 0xFF) {
        msg [index++] = recv_byte;
    }
    else if (0 < index)  {
        msg [index++] = recv_byte;

        if (MsgLen == index)  {
            process_msg ();
            index = 0;
        }
    }
    else  {
        sprintf (s, "%s: bad byte - 0x%02x", __func__, recv_byte);
        Serial.println (s); 
    }
}

// -----------------------------------------------------------------------------
void simulate()
{
    const byte sensor_bytes[] =
    {
        0x0A, 0x0B, 0x0C, 0x0D,
        0xFF, 0x01, 0x00, 0x02, 0x3F, 0x00, 0xc3,
        0xFF, 0x00, 0x00, 0x04, 0x3F, 0x00, 0xc4,
        0xFF,
        0xFF, 0x00, 0x00, 0x04, 0x3F, 0x00, 0xc4,
        0xCC,
        0xFF, 0x03, 0x00, 0x08, 0x00, 0x3F, 0xcb,
        0x00, 0x08, 0x5A, 0x3F, 0xA5,
        0xFF, 0x05, 0x80, 0x3F, 0x41, 0x80, 0x3F,
        0xD2, 0x5A, 0x3F, 0xA5,
        0xFF, 0x06, 0x00, 0x10, 0x00, 0x3F, 0x00,
        0xFF, 0x07, 0x07, 0x08, 0x09, 0x00, 0x00, 0x00
    };

    for (uint8_t i = 0; i < sizeof(sensor_bytes); i++)
        process_byte (sensor_bytes[i]);
}

void setup()
{
    Serial.begin( 115200 );
    simulate();
}

void loop()
{
    if (Serial.available ())  {
        process_byte (Serial.read ());
    }
}

Thanks for the input. But this code is way beyond my programming skills, sorry man.
It would take me like a day to figure out what it does.

At the moment I'm reading into the protocol:
pelco-ptz-protocols-d-protocol-revision-5.0.1.pdf (659.5 KB)

Seems my assumptions were wrong and I also have to sent a reply to a command.

I have the simulation implemented into my final code / hardware setup now.
Before going any further I first have to test it with the real hardware and see what it sends over serial.

it's not about programming skill, it's about organization and structure, layers or processing

you call process_byte() whenever a byte is received. it handles recognizing the start of a msg, looking for 0xFF and when a complete msg is received, after 7 bytes. it calls process_msg() when a complete msg is received

process_msg() reports (prints) the complete msg and checks the chksum. if the checksum is good, it should call another function that decodes the msg

i included how loop() recognizes when a byte is available and calls process_byte() with the received byte

your simulate() should just give process_byte() a series of byte from a buffer, nothing more

you should be able to run this code connected to the device sending the msgs and if it had received a complete msg from simulate, it would process those msgs. Else, you can just skip the call to simulate()

Ah ok,

Yes I agree. I have actually come up with a, let's say 'data flow program structure' like you describe.

I could describe my whole program flow here, but posting the code is probably the thing to do.

So here is my TX and RX code so far, not perfect but it seems to work and might be of help to someone someday.

TX code

#include <Streaming.h>                                 

/*
Arduino Nano Every Serial Ports:
(https://forum.arduino.cc/t/arduino-nano-every-access-to-4-serial/614358)

Serial  -> USB interface
Serial1 -> D0 (RX) / D1 (TX)      Stepper 1 PAN
Serial2 -> D7 (RX) / D2 (TX)      Stepper 2 TILT
Serial3 -> D3 (RX) / D6 (TX)      RS485     DATA
*/

/*==============================
   _____  ______ ______ __  __ ____ 
  / ___/ / ____//_  __// / / // __ \
  \__ \ / __/    / /  / / / // /_/ /
 ___/ // /___   / /  / /_/ // ____/ 
/____//_____/  /_/   \____//_/        
*/

void setup()
{
  Serial.begin(115200);               // Hardware serial for debugging over USB
  Serial1.begin(4800);                // Hardware serial data port  
}


/*==============================
    __    ____   ____   ____ 
   / /   / __ \ / __ \ / __ \
  / /   / / / // / / // /_/ /
 / /___/ /_/ // /_/ // ____/ 
/_____/\____/ \____//_/   
*/

void loop(){

  int ack_timeout = 300;                              // Max time to wait for ack
  unsigned long time_sent = millis();                 // Mark the time of sending
  unsigned long time_waiting = 0;                     // The time the loop has been waiting for an ack
  bool ack_received = false;

  while(Serial1.available()) Serial1.read();          // Empty serial buffer for ack
  transmit();                                         // Send out one message/byte
    
  while(time_waiting < ack_timeout)                                    
  {
    if(Serial1.available() >= 4)
    {
      ack_received = true;
      byte ack[4]; 
      Serial1.readBytes(ack, 4);
      Serial.println(ack[3], HEX);
      break;
    }
    time_waiting = millis() - time_sent;
  }

  if(!ack_received) Serial << "ack_timeout" << endl;

  //delay(5);
}


/*=========================================================
  ______ ____   ___     _   __ _____  __  ___ ____ ______
 /_  __// __ \ /   |   / | / // ___/ /  |/  //  _//_  __/
  / /  / /_/ // /| |  /  |/ / \__ \ / /|_/ / / /   / /   
 / /  / _, _// ___ | / /|  / ___/ // /  / /_/ /   / /    
/_/  /_/ |_|/_/  |_|/_/ |_/ /____//_/  /_//___/  /_/     
                                                         
Function      Byte1     Byte2       Byte3     Byte4     Byte5                     Byte6                       Byte7
              Start     Address     CMD1      CMD2      Data1                     Data2                       Checksum
              
Left          0xFF      Address     0x00      0x04      Pan Speed 0x01 - 0x3F     0x00                        SUM
Right         0xFF      Address     0x00      0x02      Pan Speed 0x01 - 0x3F     0x00                        SUM
Up            0xFF      Address     0x00      0x08      0x00                      Tilt Speed 0x01 - 0x3F      SUM
Down          0xFF      Address     0x00      0x10      0x00                      Tilt Speed 0x01 - 0x3F      SUM
 */

void transmit(){

  static int index = 0;

/* Consistent data ===========================

  const uint8_t bytes_to_send[][7] =            
  {
    {0xFF, 0x11, 0x1A, 0xFB, 0x5C, 0x6D, 0xEF},     //  1     1 start byte, 5 data bytes, 1 checksum (calculated )
    {0xFF, 0x01, 0x00, 0x02, 0x3F, 0x00, 0x42},     //  2     Message right
    {0xFF, 0x22, 0x38, 0x32, 0x3F, 0xE5, 0xB0},     //  3
    {0xFF, 0x33, 0xE5, 0x4B, 0x3F, 0x4A, 0xEC},     //  4
    {0xFF, 0x44, 0x99, 0x2A, 0x31, 0x12, 0x4A},     //  5
    {0xFF, 0x01, 0x00, 0x04, 0x3F, 0x00, 0x44},     //  6     Message left
    {0xFF, 0x55, 0x99, 0x4A, 0x5A, 0x3F, 0xD1},     //  7
    {0xFF, 0x66, 0x8D, 0x38, 0x1E, 0x3F, 0x88},     //  8
    {0xFF, 0x77, 0x5A, 0x3F, 0xA5, 0x8D, 0x42},     //  9
    {0xFF, 0x88, 0x3F, 0x41, 0x80, 0x3F, 0xC7},     // 10
    {0xFF, 0x01, 0x00, 0x08, 0x00, 0x3F, 0x48},     // 11     Message up
    {0xFF, 0x99, 0xA5, 0xBB, 0x1E, 0xD5, 0xEC},     // 12
    {0xFF, 0x0A, 0x88, 0x90, 0x00, 0x20, 0x42},     // 13
    {0xFF, 0x02, 0x00, 0x14, 0x20, 0x00, 0x26},     // 14
    {0xFF, 0x01, 0x00, 0x10, 0x00, 0x3F, 0x50}      // 15     Message down
  };

  Serial1.write(bytes_to_send[index], 7);
  //Serial << "Sending message nr: " << index+1 << endl;
  if(++index == 15) index = 0;
  
//*/

// Data with errors =========================

  const uint8_t bytes_to_send[] =
  {
  //start addr  cmd1  cmd2  data1 data2 chcks 
    0xFF, 0x11, 0x1A,       0x5C, 0x6D, 0xEF,     //  1     << ERROR, missing data
    0xFF, 0x01, 0x00, 0x02, 0x3F, 0x00, 0x42,     //  2                                 Valid: Message right
    0xFF, 0x22, 0x38, 0x32,                       //  3     << ERROR, missing data
    0xFF, 0x33, 0xE5, 0x4B, 0x3F, 0x4A, 0xEC,     //  4                                 Valid
    0xFF, 0x44, 0x99, 0x2A, 0x31, 0x12, 0xFF,     //  5     << ERROR, 0xFF in message
    0xFF, 0x01, 0x00, 0x04, 0x3F, 0x00, 0x44,     //  6                                 Valid: Message left
    0xFF, 0x55, 0x99, 0x4A, 0x5A, 0x3F, 0xD1,     //  7                                 Valid
                      0x38, 0x1E, 0x3F, 0x88,     //  8     << ERROR, missing data
    0xFF, 0x77, 0x5A, 0x3F, 0xA5, 0x8D, 0x42,     //  9                                 Valid
    0xFF, 0x88, 0x3F, 0x41, 0x80, 0xFF, 0xC7,     // 10     << ERROR, 0xFF in message
    0xFF, 0x01, 0x00, 0x08, 0x00, 0x3F, 0x48,     // 11                                 Valid: Message up
    0xFF, 0x99, 0xA5, 0xBB, 0x1E, 0xD5, 0xEC,     // 12                                 Valid
    0xFF, 0x0A, 0xFF, 0x90, 0x00, 0x20,           // 13     << ERROR, missing data      0xFF + 0x90, 0x00, 0x20, 0x02, 0x00, 0x14
          0x02, 0x00, 0x14, 0x20, 0x00, 0x26,     // 14     << ERROR, missing data      combine to one 'valid' message
    0xFF, 0x01, 0x00, 0x10, 0x00, 0x3F, 0x50      // 15                                 Valid: Message down                  
  };

  const size_t bytes_to_send_size = sizeof(bytes_to_send) / sizeof(bytes_to_send[0]);

  Serial1.write(bytes_to_send[index]);

  if(++index == bytes_to_send_size) index = 0;
  
//*/
   
} // End void transmit

RX code

#include <Streaming.h>          // For serial debugging output - https://www.arduino.cc/reference/en/libraries/streaming/

/*
Arduino Nano Every Serial Ports:
(https://forum.arduino.cc/t/arduino-nano-every-access-to-4-serial/614358)

Serial  -> USB interface
Serial1 -> D0 (RX) / D1 (TX)      Stepper 1 PAN
Serial2 -> D7 (RX) / D2 (TX)      Stepper 2 TILT
Serial3 -> D3 (RX) / D6 (TX)      RS485     DATA
*/

// PELCO D MESSAGES ====================================
byte addr = 0x01;                                                 // Address of this receiving device
const int  num_databytes = 6;                                     // Number of data bytes in Pelco message
byte new_msg[num_databytes];                                      // Message array to store incoming bytes 
byte new_msg_checksum = 0;                                        // Byte to hold the checksum of a received message 


/*=====================================
   _____  ______ ______ __  __ ____ 
  / ___/ / ____//_  __// / / // __ \
  \__ \ / __/    / /  / / / // /_/ /
 ___/ // /___   / /  / /_/ // ____/ 
/____//_____/  /_/   \____//_/        
*/

void setup() {

  Serial.begin(115200);               // Hardware serial for debugging
  Serial3.begin(4800);                // Hardware serial data port
}


/*==============================
    __    ____   ____   ____ 
   / /   / __ \ / __ \ / __ \
  / /   / / / // / / // /_/ /
 / /___/ /_/ // /_/ // ____/ 
/_____/\____/ \____//_/   
*/

void loop() {

  if(Serial3.available())
  {
    if(process_byte(Serial3.read()))
    {
      // A new message and checksum are ready to be read from:
      // msg > byte new_msg[addr,cmd1,cmd2,dta1,dta2,chks]  where chks is the checksum as received with the message
      // The checksum calculated from addr,cmd1,cmd2,dta1,dta2 is stored in:
      // chk > byte new_msg_checksum

      byte ack[] = {0xFF, addr, 0x00, new_msg_checksum};      // ack message: start 0xFF, own address, alarm byte, calculated checksum
      Serial3.write(ack, 4);                                  // Write 4 bytes of ack over serial

      // Up until this point in the code an ack with calculated 
      // checksum is sent back to the sending device to confirm reception
      // For simple PT motor control only direction (R L U D) 
      // and the speed are needed, these messages are filtered out

      /* Motor control messages, direction and speed
              adrs  cmd1  cmd2  dta1  dta2  chk  
      msg     [0]   [1]   [2]   [3]   [4]   [5]    
      right:  0x01, 0x00, 0x02, 0x##, 0x00, 0x##               dat1 and dta2 can range from 0x00 to 0x3F    
      left:   0x01, 0x00, 0x04, 0x##, 0x00, 0x##     
      up:     0x01, 0x00, 0x08, 0x00, 0x##, 0x##     
      down:   0x01, 0x00, 0x10, 0x00, 0x##, 0x##      
      */

      switch(new_msg[2])
      {
        case 0x02:
          Serial << "       Go right at speed:" << new_msg[3] << endl;
          break;
        case 0x04:
          Serial << "       Go left  at speed:" << new_msg[3] << endl;
          break;
        case 0x08:
          Serial << "       Go up    at speed:" << new_msg[4] << endl;
          break;
        case 0x10:
          Serial << "       Go down  at speed:" << new_msg[4] << endl;
          break;
      }
    }
  } 
} // end loop


/*===============================================================================     
    ____   ____   ____   ______ ______ _____ _____    ____ __  __ ______ ______
   / __ \ / __ \ / __ \ / ____// ____// ___// ___/   / __ )\ \/ //_  __// ____/
  / /_/ // /_/ // / / // /    / __/   \__ \ \__ \   / __  | \  /  / /  / __/   
 / ____// _, _// /_/ // /___ / /___  ___/ /___/ /  / /_/ /  / /  / /  / /___   
/_/    /_/ |_| \____/ \____//_____/ /____//____/  /_____/  /_/  /_/  /_____/  
 */

bool process_byte( byte recv_byte )
{
  static int index = 0;                                     // Index to place received bytes at
                                                            // Declared static to keep value between function calls
  if (recv_byte == 0xFF)
  {
    index = 0;                                              // A startbyte has been received, set index to zero
    return false;                                           // Return false for 'new message ready'
  }
  else
  {
    new_msg[index] = recv_byte;                             // Place receieved byte into message array
    
    if ( ++index == num_databytes )                         // After placing a byte at the last message array index,
    {                                                          
      index = 0;                                            // reset the index to prevent 'out of index' error 

      // Checksum
      new_msg_checksum = 0;
      for(int i=0;i<=4;i++) new_msg_checksum += new_msg[i]; // The checksum is stored in a byte variable 
                                                            // When summing only the 8 least significant bits will
                                                            // be left in this byte, overflow will be discarded

      return true;                                          // Return True to indicate a new message and checksum are ready to be read
    }
    return false;                                           // A byte has been stored in new_msg[] but 
  }                                                         // new_msg[] is not yet complete and checked
}