Sabertooth Kangaroo icon Sabertooth Kangaroo Movement panel enabling Kangaroo & Sabertooth encoder-based speed/velocity control, UART/PC serial options, tuning and encoder setup. Try it →
Asked — Edited

UART Byte Array Communication Explained

Hi @Athena,

I'm asking about the best way to communicate via UART between an EZB4 (JavaScript running on the EZB) and an Arduino Mega (C++). I'm a beginner and want to keep the implementation simple but reliable. My understanding is that sending a String is not necessary and that sending an array of bytes would be a better approach. I'm using the EZB4 hardware UART functions (UART.hardwareUart*) on the EZB side and Serial3 on the Arduino Mega side.

To clarify the hardware/ARC context: the Arduino Mega is connected to the EZB4 UART (hardware UART lines). The EZB is handling the real-time hardware UART I/O, and this runs inside an ARC project where ARC scripts manage the higher-level logic. The EZB code is a single JavaScript script running on the EZB (built-in ARC/EZB scripting). The Arduino code is standard C++ running on the Mega. This is not using a separate robot skill for the serial link; the UART I/O is performed by the EZB firmware calls from the script.

I have included my C++ (Arduino) TX and RX code and my JavaScript (EZB4) TX/RX code below for your evaluation. Could you rewrite the code to reliably send data using an array-of-bytes format?

Notes:

  • The Arduino C++ TX and RX must be separate sketches (not combined).
  • The EZB4 code runs as a single JavaScript script on the EZB (using built-in ARC/EZB scripting).
  • This is part of an ARC project where the EZB handles the real-time hardware UART and ARC scripts manage higher-level logic.

C++
Data TX

if (run == 1) {
   bufferFlush();
   totalStepsTaken = (abs(combinedStepsTaken));
   delay(50);
   int byte0 = totalStepsTaken & 0xFF;
   int byte1 = (totalStepsTaken >> 8) & 0xFF;
   int byte2 = (totalStepsTaken >> 16) & 0xFF;
   int byte3 = (totalStepsTaken >> 24) & 0xFF;
   Serial3.write('T');  //Header for Total Steps Taken
   // Send the four bytes one by one.
   Serial3.write(byte0);
   Serial3.write(byte1);
   Serial3.write(byte2);
   Serial3.write(byte3);
   Serial.print("Sent   ");
   Serial.println('T'); 
   Serial.print("Left F+B Steps:  ");
   Serial.println(abs(LeftFrontWheel.currentPosition()) + abs(LeftBackWheel.currentPosition()));
   Serial.print("Right F+B Steps:  ");
   Serial.println(abs(RightFrontWheel.currentPosition()) + abs(RightBackWheel.currentPosition()));
   Serial.print("Sent combinedStepsTaken:  ");
   Serial.println(totalStepsTaken);
   Serial.println("  ");
   delay(100);
   }

C++
Data RX

 
while ( run == 1) {
if (Serial3.available() >= 1 ) {
     int incomingByte = Serial3.peek(); // do not remove it yet
     // Check if this byte is our STOP command (assume 'x' is the stop command)

    if ( incomingByte == 'm') {
    Serial3.read();
    miss = true;
    actualCourse = desiredCourse;
    }

  if (incomingByte == 'x') {
     // Consume the byte and stop motors
     Serial3.read();  
     Serial.print("Received STOP command: ");
     Serial.println('x');
     stopMotors();
     goto Main; // jump out to the Main section
     }
 if ( incomingByte == 'c') {
    Serial3.read();
    Serial.print( "(Received obs scan complete) command  ");
    Serial.println('c');
    }
    }

  if (Serial3.available() >= 1) {
     // Read and check the header byte (expected to be 64)
     int headerByte = Serial3.read();

  if (headerByte == 64) { 
     int32_t b0 = Serial3.read();
     int32_t scaledValue = (b0);   
     actualCourse = scaledValue;
     Serial.print(" ..............----------------------------  rec'd actualCourse:  ");
     Serial.println( actualCourse,4); 

     lastActualCourse = actualCourse;
     initial = true;    
     miss = false;
     loopct = 0;
     } 
     }      

  if (miss == true ) {
     Serial.print( "                                     Received Missed command:  ");
     Serial.println('m');
      actualCourse = lastActualCourse;
  }

    combinedStepsTaken = abs(LeftFrontWheel.currentPosition()) + abs(LeftBackWheel.currentPosition())
    + abs(RightFrontWheel.currentPosition()) + abs(RightBackWheel.currentPosition());

    if (abs(combinedStepsTaken) > combinedStepsDesired) {
    stopMotors();
    goto Main;
    }
    loopct++;
    }

JavaScript Data TX and RX (single script on the EZB)

var lastActualCourse = null;
setVar("$obsDet", 0);
setVar("$missedIR", 0);
setVar("$obsScanEnd", 0);

while(true) { 
 

//Data RX

 if (UART.hardwareUartAvailable(0) >= 5) {  //5
       var header = UART.hardwareUartReadString(0,1); 
       
      if (header == 'D'){  
       var desiredCourseScaled = UART.hardwareUartRead(0, 1);  
       setVar("$desiredCourse", desiredCourseScaled[0]);  
       print("rec'd desiredCourse:  " +  getVar("$desiredCourse")+ "    cyc:  " + getVar("$cycleCount"));
       
       } else if (header == 'T') {
       print("Header: " + header);
       // read all 4 bytes from uart #0 that we need for this condition into the array tmpRead
       var TSTRead = UART.hardwareUartRead(0, 4);
       // tmpRead[3,2,1,0] are denoted as they were sent 0,1,2,3
       var totalStepsTaken = (( TSTRead[3] << 24) | ( TSTRead[2] << 16) | ( TSTRead[1] << 8) | TSTRead[0]);
       print("totalStepsTaken: " + totalStepsTaken);
       print( "CYC: " + getVar("$cycleCount") + "   $desiredCourse: " + getVar("$desiredCourse") + "   $actualCourse: " + getVar("$actualCourse"));
       sleep(100); 
       
       controlCommand("$IR_1", "PauseOff");
       sleep( 10 );
       controlCommand("IR_STEP", "ScriptStop");
       sleep( 10 );
       controlCommand("NRS_71.8_58_TEST", "ScriptStop");
       sleep( 10 );
       halt();
       }
       }   
       
   
        
        
        
   // Data TX     
        
        if (getVar("$obsDet") == 1) {
         UART.hardwareUartWrite(0, ['x'.charCodeAt(0)]); 
         sleep(5);
         print( "sent to ARDU...obsDet:  " + getVar("$obsDet"));
       print("OBS IR STOP:  " + getVar("$IRADC"));  
       }
       
       
       if ( getVar("$missedIR") == 1) {
           UART.hardwareUartWrite(0, ['m'.charCodeAt(0)]); 
           print("sent to ARDU...missedIR:  " + getVar("$missedIR"));
           }
       
       
       if ( getVar("$obsScanEnd") == 1 ) {
           UART.hardwareUartWrite(0, ['c'.charCodeAt(0)]); 
           print("sent to ARDU...Scan for obs ended:  " + getVar("$obsScanEnd"));
          }
        
       
        if(getVar("$actualCourse") != lastActualCourse) { 
        var scaledValue = getVar("$actualCourse");  
        //Send as whole number to Arduino:
        var byte0 = scaledValue & 0xFF;
        var header = [64];
        // Combine the header with the 4 bytes and send via UART
        UART.hardwareUartWrite(0, header.concat([byte0]));
       lastActualCourse = getVar("$actualCourse"); 
       print(" $actualCourse:  " + getVar("$actualCourse"));  
       }         
            
        sleep(200); 
       }


Related Hardware (view all EZB hardware)
EZ-B V4 by EZ-Robot
EZ-B v4/2 robot controller: dual Cortex ARM, Wi-Fi, audio/video, 24 servo/digital ports, I2C/UART, camera and Synthiam ARC control for custom robots
Wi-Fi / USB
Servos 24
Camera
Audio
UART 3
I2C
ADC 8
Digital 24

ARC Pro

Upgrade to ARC Pro

ARC Pro is more than a tool; it's a creative playground for robot enthusiasts, where you can turn your wildest ideas into reality.

Author Avatar
PRO
Synthiam
LinkedIn Thingiverse Twitter YouTube GitHub
#1   — Edited

There's a lot of information missing from the code snippets that you shared - so it's very difficult for athena or myself to understand what's going on. It would be easier if you shared your protocol definition document. If you don't have one, it's time to create one before you start moving forward. You're storing too much in your head and there's a lot of uncertainty between byte counts.

A protocol definition would be something that you can do in excel or google sheets or even notepad. But essentially you define what a "packet" looks like for each command. Here's an example...


Protocol Notes


  • Packets from ARC to Arduino have a header 0xf0
  • Packets from Arduino to ARC have a header 0x0f
  • Packet size varies based on the command byte
  • Packets do not have an acknowledgment
  • Reading from uart is blocking until all expected bytes have been received

ARC -> Arduino Commands


These are command packet definitions ARC will send to the Arduino.

1. Move servo (5 bytes)

This will instruct a servo to be moved on a servo port to a position that is specified as a 16 bit unsigned int

  • byte 0: header (0xf0)
  • byte 1: command (0x00) (0 means move servo)
  • byte 2: servo to move (value between 0-24 representing the servo port)
  • byte 3: position to move servo low byte of unsigned 16 bit integer
  • byte 4: position to move servo high byte of unsigned 16 bit integer

2. Send Movement Command (4 bytes)

This will instruct the robot to start moving in a direction

  • byte 0: header (0xf0)
  • byte 1: command (0x01) (1 means move robot)
  • byte 2: direction to move (0: forward, 1: turn left, 2: turn right, 3: reverse, 4: stop)
  • byte 3: speed to move between 0-255. This has no effect when the STOP direction is sent so the value will be ignored

Arduino <- ARC Commands


These are command packet definitions Arduino will send to ARC.

1. Distance Detection Response (4 bytes)

Sends a message to ARC of a distance detected on one of the ultrasonic sensors

  • byte 0: header (0x0f)
  • byte 1: sensor (0: left sensor, 1: center sensor, 2: right sensor)
  • byte 2: distance value low byte of unsigned 16 bit integer
  • byte 3: distance value high byte of unsigned 16 bit integer

@Athena - please create a protocol definition document similar to my example that reflects your understanding of the protocol from the original question. It will be easier to get a sense of what the protocol looks like before we start writing code.

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#2  

Absolutely - based on the snippets you shared, here’s a clean protocol definition draft that reflects what I can infer from your current setup.

This is important because your current code mixes:

  • single-byte commands
  • header + payload messages
  • different message lengths
  • blocking assumptions
  • and at least one case where the receive-side logic appears to expect a different packet length than what is actually sent

That makes debugging much harder than it needs to be.


Proposed UART Protocol Definition

Protocol Notes

  • Transport: UART between EZB4 ARC JavaScript and Arduino Mega C++
  • Arduino TX/RX: Serial3
  • ARC/EZB TX/RX: UART.hardwareUart*() on UART index 0
  • Packet-based communication: All messages are treated as packets with a defined structure
  • Recommended design: Use fixed packet lengths per command
  • No String parsing for data payloads: use byte arrays for reliability
  • Single-byte commands are allowed, but should still be clearly defined as command packets
  • Byte order: multi-byte numeric values are sent little-endian
    • low byte first
    • high byte second
    • etc.

Arduino ARC Commands

1. Total Steps Taken Report

Purpose: Send the robot’s total step count from Arduino to ARC.

Packet Size: 5 bytes

  • Byte 0: Header 0x54 ('T')
  • Byte 1: Total steps byte 0 (LSB)
  • Byte 2: Total steps byte 1
  • Byte 3: Total steps byte 2
  • Byte 4: Total steps byte 3 (MSB)

Data Type: Signed or unsigned 32-bit integer
Endian: Little-endian

Example: If totalStepsTaken = 123456, Arduino sends:

54 40 E2 01 00

2. Actual Course Report

From your JS code, ARC appears to expect an Arduino packet beginning with:

  • Header: 0x40 ('@')
  • Payload: 1 byte in current JS, though the Arduino code shown is not fully matching this format

Observed/Implied Packet Format

Packet Size: 2 bytes

  • Byte 0: Header 0x40
  • Byte 1: Course value or course-scaled value

However, your JavaScript currently treats actualCourse like a larger numeric value, while the Arduino RX code only reads one byte after the header.

So this packet should be clarified.

Better definition if it is meant to be a single-byte value:

  • Byte 0: Header 0x40
  • Byte 1: actualCourse as 0-255

If it is meant to be a multi-byte number:

Then the packet should be expanded and both sides updated to match.


ARC Arduino Commands

1. Stop Command

Purpose: Tell Arduino to stop all motors immediately.

Packet Size: 1 byte

  • Byte 0: Command 0x78 ('x')

Notes:

  • This is currently implemented as a single-byte command
  • Arduino checks for 'x' and calls stopMotors()

2. Missed IR Command

Purpose: Notify Arduino that IR tracking was missed.

Packet Size: 1 byte

  • Byte 0: Command 0x6D ('m')

Notes:

  • Arduino sets:
    • miss = true
    • actualCourse = desiredCourse

3. Obstacle Scan Complete Command

Purpose: Tell Arduino that obstacle scan is complete.

Packet Size: 1 byte

  • Byte 0: Command 0x63 ('c')

4. Desired Course Command

From the JavaScript:

  • ARC writes a header value of 64 (0x40)
  • Then appends one byte (byte0) from getVar("$actualCourse")

Current Observed Packet Format

Packet Size: 2 bytes

  • Byte 0: Header 0x40
  • Byte 1: Course value low byte

Notes:

  • This appears to be intended as a command from ARC to Arduino
  • But the naming is confusing because Arduino RX code also appears to treat 0x40 as an incoming header for actualCourse
  • The code needs a clearer command name, such as:
    • SET_ACTUAL_COURSE
    • SET_DESIRED_COURSE
    • COURSE_UPDATE

Protocol Issues I Observed

These are the main mismatches visible in the snippets:

1. Header confusion

You are using:

  • 'T' for total steps
  • 'x', 'm', 'c' for commands
  • 64 (0x40) for another packet

But there is no central protocol spec defining what each header means.


2. Byte-count mismatch risk

Your JavaScript has:

if(UART.hardwareUartAvailable(0) >= 5)

Then it reads:

  • 1 byte for header
  • 4 bytes for total steps

That part is reasonable for the 'T' packet.

But the Arduino RX code shown for the 0x40 packet only reads:

  • 1 header byte
  • 1 payload byte

So if you intended anything larger than 1 byte there, the code is incomplete.


3. peek() + read() logic can be fragile

On Arduino, using peek() to inspect a single byte and then later reading again can work, but it’s easy to mis-handle if bytes arrive at different times or if the buffer contains more than one packet.

A more reliable approach is:

  • read a known header byte
  • then read the exact number of expected payload bytes
  • process the packet only when all bytes are available

4. hardwareUartReadString() is not ideal for binary packet parsing

In your JS, this line:

var header = UART.hardwareUartReadString(0,1);

works for ASCII-style headers, but for binary protocols it’s better to use:

var header = UART.hardwareUartRead(0, 1)[0];

That way you’re working with raw bytes consistently.


Recommended Clean Protocol Version

If you want to simplify this protocol, I’d suggest something like this:

ARC Arduino

0x6D - Missed IR

  • byte 0: 0x6D

0x78 - Stop

  • byte 0: 0x78

0x63 - Obstacle scan complete

  • byte 0: 0x63

0x40 - Set course

  • byte 0: 0x40
  • byte 1: course value 0-255

Arduino ARC

0x54 - Total steps taken

  • byte 0: 0x54
  • byte 1: steps byte 0
  • byte 2: steps byte 1
  • byte 3: steps byte 2
  • byte 4: steps byte 3

0x44 - Actual course

  • byte 0: 0x44
  • byte 1: course value 0-255

Why this is better

This gives you:

  • one header byte per packet
  • a clear command meaning
  • fixed packet sizes
  • easy parsing on both ends
  • no string conversion required
  • no ambiguous "what does 64 mean?" issue

Next Step

If you want, I can do one of these next:

  1. Rewrite your protocol document fully in a clean table format
  2. Rewrite both the Arduino C++ sketches and ARC JavaScript using byte arrays
  3. Convert your current code into a packet parser with a proper header + length structure

If you’d like, I can produce the final protocol definition document first in a copy/paste-friendly format, and then follow with the code rewrite.


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.