Augmented Reality icon Augmented Reality Overlay PNG/JPG images in real-time onto detected objects, faces, colors or glyphs using ARC Camera tracking; attach, preview, detach. Try it →
Asked — Edited
Resolved Resolved by Athena!

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

Related Robot Skills (view all robot skills)
Add custom EZ-Script functions in C# or JS; intercept unknown calls, parse parameters and return values via AdditionalFunctionEvent.
Script by Synthiam
Multi-language ARC Script: build, run and debug Blockly, JavaScript, EZ-Script or Python with Intellisense, run/save/load and Roboscratch support

ARC Pro

Upgrade to ARC Pro

Join the ARC Pro community and gain access to a wealth of resources and support, ensuring your robot's success.

#1  

Hi @Athena, Below you will find my Protocol Document for Arduino and ARC.  I have provided an overview to explain my project scope.  I have listed all the UART TX and RX 'headers' and the values they represent.  I am at a loss for how else to describe it.  I am hoping you can understand my document and what else would be required base upon what I have included.  My objective is reliable UART communication between the Arduino and ARC.   Can you rewrite the document in a clean table format so my objective can be accomplished?     Thank you Jack

Robot Overview:

A rover driven by 4 stepper motors rolling on 4" mecanum wheels and powered by a 24v 5000mAh lithium battery. Individual buck converters supply the appropriate DC voltage to the EZB4, Arduino Mega, IR distance measuring sensor, and a single sensor servo.  The principal purpose of this rover is to explore and develop a navigation system based upon a Sharp IR distance measuring sensor.  Overall controlling architecture is accomplished via Synthiam ARC on the EZB4.  Certain high CPU demands such as stepper motor control and Desired Bearing computations are accomplished by the Arduino.  Necessary communication between ARC and Arduino is via UART.
The purpose of this document is to facilitate UART communication by stating "What" needs to be sent, "Type" of data, and "Size" expected.

SECTION 1----------------------------

Arduino Protocol:

The Arduino has two Loops (RUN = 0) and (RUN = 1) within a "while" loop.  (RUN=0) loops to receive data from ARC before initiating forward navigation movement, and to pivot or perform other desired mecanum wheel maneuvers prior to initiating forward navigation.  (RUN=1) allows forward navigation to begin, counting motor steps and computing Desired Bearing based upon distance traveled.  PWM is applied to motors based upon the relationship of Desired Bearing and UART received Actual Bearing from ARC.  Periodically, Desired Bearing is UART sent to ARC so that the operator can monitor deviation in navigation.  Other than certain UART communication from ARC (discussed in ARC Protocol), the only other UART data sent to ARC is Total Steps Taken at the arrival of the destination waypoint.

 ARDUINO RUN = 0 loop:
         UART RX

         incoming Byte: 'RP' Header sent by ARC meaning Right Pivot.
         incoming Byte: 'number of steps' computed by ARC for the distance traveled to intercept the Desired Bearing.  A positive whole number less than 10,000 steps.
 
         incoming Byte:  'LP' Header sent by ARC meaning Left Pivot.
         incoming Byte:  'number of steps' computed by ARC for the distance traveled to intercept the Desired Bearing.  A positive whole number less than 10,000 steps.

         incoming Byte:  'X' sent by ARC meaning Stop all motors.  Sent for any stop request, normal or emergency.

         incoming Byte:  'WPT' Header sent by ARC meaning Waypoint Data for navigation to the destination waypoint is being sent. When it is received, RUN = 1, and UART sends a confirmation to ARC ('NAV') to  ControlCommand Navigation script to start.

         The following 9 packets are received after Header 'WPT':

         incoming Byte:  'pathTotalDistance' meaning Path Total Distance.  A large positive whole number (120,000 max).
         incoming Byte:  'combinedStepsDesired' meaning Combined Steps Desired.  A large positive whole number (84,000 max).
         incoming Byte:  'oppositeDistance'  meaning Opposite Distance.  A large positive whole number (24,000 max).
         incoming Byte:  'bearingAlignment' meaning Bearing Alignment.  A ('DA' = Direct Alignment) or ('OA' = Offset Alignment).
         incoming Byte:  'reflectorBearing' meaning Reflector Bearing.  A positive whole number between 0-180.
 
         incoming Byte:  'sensorPrime' means Sensor Prime.  A positive whole number between 1 and 10.
It is the number of degrees (added or subtracted) to the Reflector Bearing which derives the Desired Bearing.  Note: if the Reflector Bearing is greater than 90degs, Sensor Prime is added to the Reflector Bearing to derive the Desired Bearing.  Conversely, if the Reflector Bearing is 90degs or less, Sensor Prime is subtracted to the Reflector Bearing to derive the Desired Bearing.

         incoming Byte:  'desiredBearing' meaning Desired Bearing.  A positive fractional number between 0.00-180.00. During 'OA' Bearing Alignment Operations, it will be modified by a trig function with two decimals (ex: 78.35 deg).  Otherwise, during 'DA' Bearing Alignment Operations, it will remain a positive fractional number between 0.00-180.00 with two decimals (ex: 85.00).
 
         incoming Byte: 'actualBearing' meaning Actual Bearing.  A positive whole number between 0-180. (never a fractional number)
 
         incoming Byte: 'trackDir' meaning Track Direction.  A ('FD' = Forward Direction) or ('RD' = Reverse Direction).
 

ARDUINO RUN = 0 loop:
         UART TX

         Sends Byte: 'NAV' to confirm receiving 'WPT' Data (9 packets).  This is a very important step because forward motion of the rover is next and without this data navigation will not be possible.

         Sends Byte: 'T' Header sent to ARC meaning Total Steps Taken.  This is a large positive whole number consisting of the sum of four stepper motors and less than 100,000 steps. It is represented in 4 bytes(0,1,2,3).  It is reported at the completion of pivots and other mecanum wheel movements during RUN=0 and Leg movement during RUN=1. Also triggered by Arduino receiving an 'X' byte. (Stop Motors)

SECTION 2----------------------------

 ARDUINO RUN = 1 loop:
         UART RX

         incoming Byte:  'M' sent from ARC meaning Missed IR.  This alerts PWM to revert to a 'zero deviation' PWM to minimize actual course divergence from the Desired Bearing. All differential motor correction is stopped and neutral PWM is applied.

         incoming Byte:  'AB'  sent from ARC meaning Actual Bearing.  Whenever a new Actual Bearing is detected in ARC it must be sent to the Arduino so that it can be compared to the Desired Bearing for mapping PWM. Actual Bearing is a positive whole number between 0-180. (never fractional)

         incoming Byte:  'X' sent by ARC meaning Stop all motors.  Sent for any stop request, normal or emergency.

 ARDUINO RUN = 1 loop:
         UART TX
 
         
          Sends Byte: 'T' Header sent to ARC meaning Total Steps Taken.  This is a large positive whole number consisting of the sum of four stepper motors and less than 100,000 steps. It is represented in 4 bytes(0,1,2,3).  It is reported at the completion of pivots and other mecanum wheel movements during RUN=0 and Leg movement during RUN=1. Also triggered by Arduino receiving an 'X' byte. (Stop Motors)

          Sends Byte 'OS' meaning Obstacle Scan.  This scan is triggered in Arduino by reaching a percentage of Path Total Distance. Normal MAP function is stopped as in a Missed IR condition.  This alerts PWM to revert to a 'zero deviation' PWM to minimize Actual Bearing divergence from Desired Bearing. Any motor correction is stopped and neutral PWM is applied.  When a new Actual Bearing is received normal MAP operation will resume.

          Sends Byte 'DB' when a new Desired Bearing has been computed. The Desired Bearing need only be sent when it changes by 1 whole number (such as 78.35deg-79.35deg).  It is being sent to ARC only for the purpose of printing, so that deviations in Actual Bearing and Desired Bearing can be monitored.  Desired Bearing is computed in one of two ways. It begins with the Reflector Bearing modified with Sensor Prime to derive at the Desired Bearing.  It is always a positive fractional number with two decimal places between 0.00-180.00.

                                     How 'DB' (Desired Bearing) is computed:

       For ('DA') Bearing Alignment Operation, when the Reflector Bearing is greater than 90deg, Sensor Prime is added to arrive at the Desired Bearing. For Reflector Bearing 90deg or less, Sensor Prime is subtracted to arrive at the Desired Bearing. Desired Bearing becomes a positive fractional number between 0.00 and 180.00.

       For ('OA') Bearing Alignment Operation when the Reflector Bearing is greater than 90deg, Sensor Prime is added to arrive at the Desired Bearing. For Reflector Bearing 90deg or less, Sensor Prime is subtracted to arrive at the Desired Bearing. Desired Bearing is further modified by a trig function, and becomes a positive fractional number between 0.00 and 180.00.

 
      Example: 'DA' Bearing Alignment Operation                                                                                                
      Reflector Bearing = 90
      ( Reflector Bearing - Sensor Prime ) = Desired Bearing                    

      Example:  'OA'  Bearing Alignment Operation
      Reflector Bearing = 97
      ( Reflector Bearing + Sensor Prime ) = ( Desired Bearing(trig function))

                            Informational note on mapping Desired Bearing and Actual Bearing:

Desired Bearing will always produce a fractional number for ('OA') operations such as (79.35) deg.  Desired Bearing will always produce a fractional number for ('DA') operations such as (79.00) deg. Neither of these fractional numbers can be used in the MAP function in Arduino for PWM.  So the (79.xx) deg is multiplied (79.xx) X (100) to provide a whole number.  A similar change to Actual Bearing is applied (79 X 100).  These two Bearing sums are compared and mapped for PWM.

 

SECTION 3---------------------------

ARC Protocol:
 
     UART initiates from one dedicated script titled "UART_TX_RX".  Once it is Control Commanded to start, it sends Leg Waypoint data after which it enters a loop with a sleep(200).  In this loop it is constantly looking to RX UART Headers and to TX UART Headers/data when triggered by a global variable change elsewhere in ARC scripts.

ARC Leg Waypoint Data (sent one time)
       UART TX
       (WPT) Header.
       9 packets of data sent.
 

         Send Byte: 'RP' Header sent by ARC meaning Right Pivot.
         Send Byte: 'number of steps' computed by ARC for the distance traveled to intercept the Desired Bearing.  A positive whole number    less than 10,000 steps.
 
         Send Byte:  'LP' Header sent by ARC meaning Left Pivot.
         Send Byte:  'number of steps' computed by ARC for the distance traveled to intercept the Desired Bearing.  A positive whole number less than 10,000 steps.

         Send Byte:  'X' sent by ARC meaning Stop all motors.  Sent for any stop request, normal or emergency.

         Send Byte:  'WPT' Header sent by ARC meaning Waypoint Data for navigation to the destination waypoint is being sent. When it is received, RUN = 1, and UART sends a confirmation to ARC ('NAV') to  ControlCommand Navigation script to start.

         The following 9 packets are sent after Header 'WPT':

         Send Byte:  'pathTotalDistance' meaning Path Total Distance.  A large positive whole number (120,000 max).
         Send Byte:  'combinedStepsDesired' meaning Combined Steps Desired.  A large positive whole number (84,000 max).
         Send Byte:  'oppositeDistance'  meaning Opposite Distance.  A large positive whole number (24,000 max).
         Send Byte:  'bearingAlignment' meaning Bearing Alignment.  A (DA = direct alignment) or (OA = offset alignment).
         Send Byte:  'reflectorBearing' meaning Reflector Bearing. A positive and whole number between 0-180.
 
         Send Byte:  'sensorPrime' means Sensor Prime.  A positive whole number between 1 and 10.
It is the number of degrees (added or subtracted) to the Reflector Bearing which derives the Desired Bearing.  Note: if the Reflector Bearing is greater than 90degs, Sensor Prime is added to the Reflector Bearing to derive the Desired Bearing.  Conversely, if the Reflector Bearing is 90degs or less, Sensor Prime is subtracted to the Reflector Bearing to derive the Desired Bearing.

         Send Byte:  'desiredBearing' meaning Desired Bearing.  A positive fractional number between 0.00-180.00. During 'OA' Bearing Alignment Operations, it will be modified by a trig function with two decimals (ex: 78.35 deg).  Otherwise, during 'DA' Bearing Alignment Operations, it will remain a positive fractional number between 0.00-180.00 with two decimals (ex: 85.00).
 
         Send Byte:  'actualBearing' meaning Actual Bearing.  A positive whole number between 0-180. (never a fractional number)
  
         Send Byte:  'trackDir' meaning Track Direction.  A ('FD' = Forward Direction) or ('RD' = Reverse Direction).

After the above 9 packets have been sent, a loop is entered and awaits for (NAV) Header to be received, indicating the Arduino has received the 9 packets for Leg movement to begin.  After the (NAV) Header is received, the next loop is begun and contains the following UART TX/RX functions:

SECTION 4------------------------------

   ARC UART TX/RX Loop:

    RX incoming Byte Header 'DB' meaning Desired Bearing.  A positive fractional number between 0.00-180.00. Its purpose in ARC is simply to allow monitoring of the Actual Bearing / Desired Bearing deviations.

    RX incoming Byte Header 'T' meaning Total Steps Taken.  This is a large positive whole number consisting of the sum of four stepper motors and less than 100,000 steps. It is represented in 4 bytes(0,1,2,3).  It is reported at the completion of pivots and other mecanum wheel movements during RUN=0 and Leg movement during RUN=1.

    RX incoming Byte 'OS' meaning Obstacle Scan.  Triggered by step counting in the Arduino, this incoming byte sends $obsScan = 1.  This global variable activates a 'scan for obstacles' sequence.  Meanwhile, the Arduino PWM MAP reverts to neutral motor correction.  After obstacle scan is complete, $obsScan = 0 and a new Actual Course is acquired and sent triggering normal PWM Mapping.

    $obsDet global variable is checked for = 1.  Triggered by excessive IR value. If $obsDet = 1, a Byte: 'X' is sent by ARC meaning Stop all motors.  Arduino will respond by stopping all motors and sending a Header 'T' meaning Total Steps Taken.

    Send Byte:  'X' sent by ARC meaning Stop all motors.  Sent for any stop request, normal or emergency.

    $missedIR global variable is checked for = 1. A search for the reflector is initiated and the Byte: 'M' sent from ARC meaning Missed IR.  This alerts PWM to revert to a 'zero deviation' PWM to minimize Actual Bearing divergence from Desired Bearing. Any motor correction is stopped and neutral PWM is applied.

    $actualBearing global variable is checked for update.  If new, a byte: 'AB', meaning Actual Bearing, is sent from ARC.  Whenever a new Actual Bearing is detected in ARC it must be sent to the Arduino so that it can be compared to Desired Bearing for mapping PWM. Actual Bearing is a positive whole number between 0-180 and is determined by polling the servo.

 
Author Avatar
PRO
Canada
Twitter YouTube GitHub
#2  

Absolutely - the current description is not yet a clean protocol, because it mixes:

  • ASCII strings like "RP", "WPT", "NAV"
  • single-byte commands like 'X', 'M'
  • mixed packet lengths
  • semantic names that are acting like both command labels and data values

For a reliable, expandable UART protocol, the packet format should use byte values only:

  • 1 byte header
  • 1 byte command
  • payload bytes
  • no ASCII command words
  • no double-byte "header strings"

That gives you a protocol that is easy to parse on both the Arduino and ARC sides.


Clean UART Protocol Definition

Protocol rules

Rule Description
Transport UART between EZ-B v4 ARC JavaScript and Arduino Mega
Format Binary packets only
Header 1 byte
Command 1 byte
Data 0 or more bytes depending on command
Endianness Little-endian for multi-byte numeric values
ASCII strings Not used for commands or payloads
Packet sizes Fixed per command
Parsing Read header, then command, then exact payload length

Byte Values

Packet types

Use a single shared header byte for all packets.

Field Value
Packet Header 0xA5

This makes it easy to identify valid packets.


Command Map

ARC Arduino

Command Name Command Byte Payload Size Payload Format Purpose
Stop Motors 0x01 0 None Emergency or normal stop
Missed IR 0x02 0 None Indicates lost reflector / course miss
Send Actual Bearing 0x03 2 uint16 Actual bearing multiplied by 100
Right Pivot 0x04 2 uint16 Pivot step count
Left Pivot 0x05 2 uint16 Pivot step count
Waypoint Data 0x06 variable structured payload Sends navigation waypoint block
Obstacle Scan Complete 0x07 0 None Scan finished, resume normal logic

Arduino ARC

Command Name Command Byte Payload Size Payload Format Purpose
Navigation Acknowledgement 0x10 0 None Confirms waypoint packet received
Total Steps Taken 0x11 4 uint32 Total steps taken by all motors
Desired Bearing Update 0x12 2 uint16 Desired bearing multiplied by 100
Obstacle Scan Request 0x13 0 None Requests ARC to begin obstacle scan state
Motor Status 0x14 1 uint8 Optional status code

Payload Definitions

1. Stop Motors

ARC Arduino

Byte Value
Header 0xA5
Command 0x01
Payload none

Packet size: 2 bytes


2. Missed IR

ARC Arduino

Byte Value
Header 0xA5
Command 0x02
Payload none

Packet size: 2 bytes


3. Send Actual Bearing

ARC Arduino

Actual bearing should be sent as a whole number scaled by 100.

Example:

  • 79.35 degrees becomes 7935
  • sent as a 16-bit unsigned integer
Byte Value
Header 0xA5
Command 0x03
Byte 2 Low byte of bearing
Byte 3 High byte of bearing

Packet size: 4 bytes


4. Right Pivot

ARC Arduino

Byte Value
Header 0xA5
Command 0x04
Byte 2 Low byte of step count
Byte 3 High byte of step count

Packet size: 4 bytes


5. Left Pivot

ARC Arduino

Byte Value
Header 0xA5
Command 0x05
Byte 2 Low byte of step count
Byte 3 High byte of step count

Packet size: 4 bytes


6. Waypoint Data

ARC Arduino

This should be a fixed structured packet, not a string packet.

Recommended layout:

Byte Field Type Notes
0 Header 0xA5 packet marker
1 Command 0x06 waypoint packet
2-5 Path Total Distance uint32 max 120000
6-9 Combined Steps Desired uint32 max 84000
10-13 Opposite Distance uint32 max 24000
14 Bearing Alignment uint8 0 = DA, 1 = OA
15-16 Reflector Bearing uint16 scaled by 100
17 Sensor Prime uint8 1-10
18-19 Desired Bearing uint16 scaled by 100
20-21 Actual Bearing uint16 scaled by 100
22 Track Direction uint8 0 = FD, 1 = RD

Packet size: 23 bytes


Arduino ARC Packets

Arduino ARC

Byte Value
Header 0xA5
Command 0x10
Payload none

Packet size: 2 bytes


2. Total Steps Taken

Arduino ARC

Use a 32-bit unsigned integer.

Byte Value
Header 0xA5
Command 0x11
Byte 2 Total steps byte 0
Byte 3 Total steps byte 1
Byte 4 Total steps byte 2
Byte 5 Total steps byte 3

Packet size: 6 bytes


3. Desired Bearing Update

Arduino ARC

Byte Value
Header 0xA5
Command 0x12
Byte 2 Low byte
Byte 3 High byte

Packet size: 4 bytes


4. Obstacle Scan Request

Arduino ARC

Byte Value
Header 0xA5
Command 0x13
Payload none

Packet size: 2 bytes


Data Representation Rules

Numeric conversions

Value Type Encoding
uint8 1 byte
uint16 2 bytes, low byte first
uint32 4 bytes, low byte first
Bearing values multiply by 100 before sending
Boolean states 0 or 1

What this corrects

This protocol fixes the main problems in the original document:

  • no ASCII words like "RP", "WPT", "NAV"
  • no ambiguous command meanings
  • no mixed "headers" that are actually multiple characters
  • every packet has a known length
  • all numeric values are byte-based and deterministic
  • expanded commands are easy to add later

Important recommendation

Your original design is trying to do too much with too much human-language meaning embedded in the packet names. For UART reliability, the Arduino and ARC should only care about:

  1. packet header
  2. command byte
  3. expected payload length
  4. how to decode the payload

Everything else belongs in your project documentation, not inside the packet.


Next step

If you want, I can do the next part and give you:

  1. a fully cleaned protocol document in final table form, ready to paste into Google Sheets, and/or
  2. the actual Arduino C++ and ARC JavaScript code that implements this binary packet protocol.

If you’d like, I can rewrite the protocol as a two-column "Arduino ARC" spec sheet next.


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.
#3  

@Athena, Thank you.   I appreciate your willingness to teach me what I need to know about structuring UART packets.   It seems you grasped what I need to accomplish in my plain language document of UART communication between the Arduino and ARC.  I understand now that we're sending recognizable packets that follow proper protocol for UART communication.  I love that this build will be reproducible in the future, as this protocol allows this.   I look forward to seeing how you implement this.   Can you please send a two-column "Arduino ARC" spec sheet and the actual Arduino C++ and ARC JavaScript code that implements this protocol? Thank you again. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#4  

Arduino ARC UART Spec Sheet

Below is a clean binary protocol using single-byte packet IDs and fixed, predictable payload sizes.

Packet format

Byte Meaning
0 Header = 0xA5
1 Command
2..n Payload

Rules

  • All packets start with the same header byte: 0xA5
  • All command IDs are one byte
  • No ASCII command strings are used
  • Multi-byte numbers are sent little-endian
  • Bearing values are sent as degrees * 100
  • Boolean flags are sent as 0 or 1

Command table

Direction Command ID Payload Description
ARC Arduino Stop Motors 0x01 none Stop all motion
ARC Arduino Missed IR 0x02 none IR target lost
ARC Arduino Actual Bearing Update 0x03 uint16 Bearing × 100
ARC Arduino Right Pivot 0x04 uint16 Step count
ARC Arduino Left Pivot 0x05 uint16 Step count
ARC Arduino Waypoint Data 0x06 structured block Navigation data
ARC Arduino Obstacle Scan Complete 0x07 none Resume normal tracking
Arduino ARC Navigation Acknowledgement 0x10 none Waypoint received
Arduino ARC Total Steps Taken 0x11 uint32 Total steps
Arduino ARC Desired Bearing Update 0x12 uint16 Bearing × 100
Arduino ARC Obstacle Scan Request 0x13 none Begin scan state

Waypoint packet layout

Byte Range Field Type
0 Header 0xA5
1 Command 0x06
2-5 Path Total Distance uint32
6-9 Combined Steps Desired uint32
10-13 Opposite Distance uint32
14 Bearing Alignment uint8
15-16 Reflector Bearing uint16
17 Sensor Prime uint8
18-19 Desired Bearing uint16
20-21 Actual Bearing uint16
22 Track Direction uint8

Suggested values

  • Bearing Alignment: 0 = DA, 1 = OA
  • Track Direction: 0 = FD, 1 = RD

Arduino C++ sketches

You asked for separate TX and RX sketches, so here they are.


1) Arduino TX sketch

Sends T, DB, and NAV-style responses using binary packets.

#include <Arduino.h>

static const uint8_t UART_HEADER = 0xA5;

static const uint8_t CMD_NAV_ACK = 0x10;
static const uint8_t CMD_TOTAL_STEPS = 0x11;
static const uint8_t CMD_DESIRED_BEARING = 0x12;
static const uint8_t CMD_OBS_SCAN_REQUEST = 0x13;

void sendHeaderCommand(uint8_t cmd) {
  Serial3.write(UART_HEADER);
  Serial3.write(cmd);
}

void sendUInt16(uint8_t cmd, uint16_t value) {
  Serial3.write(UART_HEADER);
  Serial3.write(cmd);
  Serial3.write((uint8_t)(value & 0xFF));
  Serial3.write((uint8_t)((value >> 8) & 0xFF));
}

void sendUInt32(uint8_t cmd, uint32_t value) {
  Serial3.write(UART_HEADER);
  Serial3.write(cmd);
  Serial3.write((uint8_t)(value & 0xFF));
  Serial3.write((uint8_t)((value >> 8) & 0xFF));
  Serial3.write((uint8_t)((value >> 16) & 0xFF));
  Serial3.write((uint8_t)((value >> 24) & 0xFF));
}

uint32_t totalStepsTaken = 0;
uint16_t desiredBearing100 = 0;

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

void loop() {
  // Example: send NAV ack once after receiving waypoint data in your real code
  // sendHeaderCommand(CMD_NAV_ACK);

  // Example: send total steps
  totalStepsTaken = 123456;
  sendUInt32(CMD_TOTAL_STEPS, totalStepsTaken);
  delay(500);

  // Example: send desired bearing update scaled by 100
  desiredBearing100 = 7935; // 79.35 degrees
  sendUInt16(CMD_DESIRED_BEARING, desiredBearing100);
  delay(500);
}

2) Arduino RX sketch

Receives ARC commands and decodes them reliably.

#include <Arduino.h>

static const uint8_t UART_HEADER = 0xA5;

static const uint8_t CMD_STOP_MOTORS = 0x01;
static const uint8_t CMD_MISSED_IR = 0x02;
static const uint8_t CMD_ACTUAL_BEARING = 0x03;
static const uint8_t CMD_RIGHT_PIVOT = 0x04;
static const uint8_t CMD_LEFT_PIVOT = 0x05;
static const uint8_t CMD_WAYPOINT_DATA = 0x06;
static const uint8_t CMD_OBS_SCAN_COMPLETE = 0x07;

enum PacketState {
  WAIT_HEADER,
  WAIT_CMD,
  WAIT_PAYLOAD
};

PacketState state = WAIT_HEADER;
uint8_t currentCmd = 0;

uint8_t payload[32];
uint8_t payloadIndex = 0;
uint8_t expectedLength = 0;

uint16_t readUInt16LE(const uint8_t* data) {
  return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}

uint32_t readUInt32LE(const uint8_t* data) {
  return (uint32_t)data[0] |
         ((uint32_t)data[1] << 8) |
         ((uint32_t)data[2] << 16) |
         ((uint32_t)data[3] << 24);
}

uint8_t getPayloadLength(uint8_t cmd) {
  switch (cmd) {
    case CMD_STOP_MOTORS:
    case CMD_MISSED_IR:
    case CMD_OBS_SCAN_COMPLETE:
      return 0;

    case CMD_ACTUAL_BEARING:
    case CMD_RIGHT_PIVOT:
    case CMD_LEFT_PIVOT:
      return 2;

    case CMD_WAYPOINT_DATA:
      return 21; // bytes after cmd:
                 // 4 + 4 + 4 + 1 + 2 + 1 + 2 + 2 + 1 = 21
    default:
      return 0xFF;
  }
}

void processPacket(uint8_t cmd, uint8_t* data, uint8_t len) {
  switch (cmd) {
    case CMD_STOP_MOTORS:
      Serial.println("STOP MOTORS");
      break;

    case CMD_MISSED_IR:
      Serial.println("MISSED IR");
      break;

    case CMD_ACTUAL_BEARING: {
      uint16_t bearing100 = readUInt16LE(data);
      float bearing = bearing100 / 100.0f;
      Serial.print("ACTUAL BEARING: ");
      Serial.println(bearing, 2);
      break;
    }

    case CMD_RIGHT_PIVOT: {
      uint16_t steps = readUInt16LE(data);
      Serial.print("RIGHT PIVOT STEPS: ");
      Serial.println(steps);
      break;
    }

    case CMD_LEFT_PIVOT: {
      uint16_t steps = readUInt16LE(data);
      Serial.print("LEFT PIVOT STEPS: ");
      Serial.println(steps);
      break;
    }

    case CMD_WAYPOINT_DATA: {
      uint32_t pathTotalDistance = readUInt32LE(&data[0]);
      uint32_t combinedStepsDesired = readUInt32LE(&data[4]);
      uint32_t oppositeDistance = readUInt32LE(&data[8]);
      uint8_t bearingAlignment = data[12];
      uint16_t reflectorBearing = readUInt16LE(&data[13]);
      uint8_t sensorPrime = data[15];
      uint16_t desiredBearing = readUInt16LE(&data[16]);
      uint16_t actualBearing = readUInt16LE(&data[18]);
      uint8_t trackDir = data[20];

      Serial.println("WAYPOINT DATA RECEIVED");
      Serial.print("pathTotalDistance: "); Serial.println(pathTotalDistance);
      Serial.print("combinedStepsDesired: "); Serial.println(combinedStepsDesired);
      Serial.print("oppositeDistance: "); Serial.println(oppositeDistance);
      Serial.print("bearingAlignment: "); Serial.println(bearingAlignment);
      Serial.print("reflectorBearing: "); Serial.println(reflectorBearing / 100.0f, 2);
      Serial.print("sensorPrime: "); Serial.println(sensorPrime);
      Serial.print("desiredBearing: "); Serial.println(desiredBearing / 100.0f, 2);
      Serial.print("actualBearing: "); Serial.println(actualBearing / 100.0f, 2);
      Serial.print("trackDir: "); Serial.println(trackDir);

      // Send NAV ack back to ARC
      Serial3.write(UART_HEADER);
      Serial3.write(CMD_NAV_ACK);
      break;
    }

    default:
      Serial.println("UNKNOWN COMMAND");
      break;
  }
}

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

void loop() {
  while (Serial3.available() > 0) {
    uint8_t b = (uint8_t)Serial3.read();

    switch (state) {
      case WAIT_HEADER:
        if (b == UART_HEADER) {
          state = WAIT_CMD;
        }
        break;

      case WAIT_CMD:
        currentCmd = b;
        expectedLength = getPayloadLength(currentCmd);

        if (expectedLength == 0xFF) {
          state = WAIT_HEADER;
        } else if (expectedLength == 0) {
          processPacket(currentCmd, nullptr, 0);
          state = WAIT_HEADER;
        } else {
          payloadIndex = 0;
          state = WAIT_PAYLOAD;
        }
        break;

      case WAIT_PAYLOAD:
        payload[payloadIndex++] = b;
        if (payloadIndex >= expectedLength) {
          processPacket(currentCmd, payload, expectedLength);
          state = WAIT_HEADER;
        }
        break;
    }
  }
}

ARC JavaScript script

This is a single ARC JavaScript script that:

  • sends waypoint packets
  • sends stop/missed IR/actual bearing packets
  • reads Arduino responses
  • uses UART.hardwareUart*
var UART_INDEX = 0;
var UART_HEADER = 0xA5;

var CMD_STOP_MOTORS = 0x01;
var CMD_MISSED_IR = 0x02;
var CMD_ACTUAL_BEARING = 0x03;
var CMD_RIGHT_PIVOT = 0x04;
var CMD_LEFT_PIVOT = 0x05;
var CMD_WAYPOINT_DATA = 0x06;
var CMD_OBS_SCAN_COMPLETE = 0x07;

var RSP_NAV_ACK = 0x10;
var RSP_TOTAL_STEPS = 0x11;
var RSP_DESIRED_BEARING = 0x12;
var RSP_OBS_SCAN_REQUEST = 0x13;

function u16ToBytes(value) {
    return [value & 0xFF, (value >> 8) & 0xFF];
}

function u32ToBytes(value) {
    return [
        value & 0xFF,
        (value >> 8) & 0xFF,
        (value >> 16) & 0xFF,
        (value >> 24) & 0xFF
    ];
}

function sendPacket(cmd, payload) {
    var packet = [UART_HEADER, cmd].concat(payload);
    UART.hardwareUartWrite(UART_INDEX, packet);
}

function sendStopMotors() {
    sendPacket(CMD_STOP_MOTORS, []);
}

function sendMissedIR() {
    sendPacket(CMD_MISSED_IR, []);
}

function sendActualBearing(bearingDegrees) {
    var scaled = Math.round(bearingDegrees * 100.0);
    sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(scaled));
}

function sendRightPivot(steps) {
    sendPacket(CMD_RIGHT_PIVOT, u16ToBytes(steps));
}

function sendLeftPivot(steps) {
    sendPacket(CMD_LEFT_PIVOT, u16ToBytes(steps));
}

function sendWaypointData(pathTotalDistance,
                          combinedStepsDesired,
                          oppositeDistance,
                          bearingAlignment,
                          reflectorBearing,
                          sensorPrime,
                          desiredBearing,
                          actualBearing,
                          trackDir) {

    var payload = [];
    payload = payload.concat(u32ToBytes(pathTotalDistance));
    payload = payload.concat(u32ToBytes(combinedStepsDesired));
    payload = payload.concat(u32ToBytes(oppositeDistance));
    payload.push(bearingAlignment & 0xFF);
    payload = payload.concat(u16ToBytes(Math.round(reflectorBearing * 100.0)));
    payload.push(sensorPrime & 0xFF);
    payload = payload.concat(u16ToBytes(Math.round(desiredBearing * 100.0)));
    payload = payload.concat(u16ToBytes(Math.round(actualBearing * 100.0)));
    payload.push(trackDir & 0xFF);

    sendPacket(CMD_WAYPOINT_DATA, payload);
}

function readPacket() {
    var available = UART.hardwareUartAvailable(UART_INDEX);
    if (available < 2) {
        return null;
    }

    var first = UART.hardwareUartRead(UART_INDEX, 1);
    if (first[0] != UART_HEADER) {
        return null;
    }

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

    if (cmdByte == RSP_NAV_ACK) {
        print("Arduino NAV ACK received");
        return { cmd: cmdByte, payload: [] };
    }

    if (cmdByte == RSP_OBS_SCAN_REQUEST) {
        print("Arduino requested obstacle scan");
        return { cmd: cmdByte, payload: [] };
    }

    if (cmdByte == RSP_TOTAL_STEPS) {
        if (UART.hardwareUartAvailable(UART_INDEX) < 4) {
            return null;
        }

        var data = UART.hardwareUartRead(UART_INDEX, 4);
        var totalSteps =
            (data[0]) |
            (data[1] << 8) |
            (data[2] << 16) |
            (data[3] << 24);

        print("Total steps taken: " + totalSteps);
        return { cmd: cmdByte, payload: data };
    }

    if (cmdByte == RSP_DESIRED_BEARING) {
        if (UART.hardwareUartAvailable(UART_INDEX) < 2) {
            return null;
        }

        var b = UART.hardwareUartRead(UART_INDEX, 2);
        var bearing100 = b[0] | (b[1] << 8);
        print("Desired bearing: " + (bearing100 / 100.0));
        return { cmd: cmdByte, payload: b };
    }

    return null;
}

//  Example Project variables
setVar("$obsDet", 0);
setVar("$missedIR", 0);
setVar("$obsScanEnd", 0);

var lastActualBearing = -1;
var lastDesiredBearing = -1;

// Send initial waypoint once
sendWaypointData(
    120000,   // pathTotalDistance
    84000,    // combinedStepsDesired
    24000,    // oppositeDistance
    0,        // bearingAlignment: 0 = DA, 1 = OA
    97.0,     // reflectorBearing
    5,        // sensorPrime
    79.35,    // desiredBearing
    79.0,     // actualBearing
    0         // trackDir: 0 = FD, 1 = RD
);

while (true) {
    // RX from Arduino
    var pkt = readPacket();
    if (pkt != null) {
        if (pkt.cmd == RSP_NAV_ACK) {
            print("Waypoint accepted by Arduino");
        }
        else if (pkt.cmd == RSP_TOTAL_STEPS) {
            print("Arduino reported total steps");
        }
        else if (pkt.cmd == RSP_OBS_SCAN_REQUEST) {
            setVar("$obsScan", 1);
        }
    }

    // TX to Arduino based on ARC variables
    if (getVar("$obsDet") == 1) {
        sendStopMotors();
        print("Sent STOP");
        setVar("$obsDet", 0);
    }

    if (getVar("$missedIR") == 1) {
        sendMissedIR();
        print("Sent MISSED IR");
        setVar("$missedIR", 0);
    }

    if (getVar("$obsScanEnd") == 1) {
        sendPacket(CMD_OBS_SCAN_COMPLETE, []);
        print("Sent obstacle scan complete");
        setVar("$obsScanEnd", 0);
    }

    var currentActualBearing = getVar("$actualBearing", -1);
    if (currentActualBearing != -1 && currentActualBearing != lastActualBearing) {
        sendActualBearing(currentActualBearing);
        lastActualBearing = currentActualBearing;
        print("Sent actual bearing: " + currentActualBearing);
    }

    var currentDesiredBearing = getVar("$desiredBearing", -1);
    if (currentDesiredBearing != -1 && currentDesiredBearing != lastDesiredBearing) {
        print("Desired bearing updated: " + currentDesiredBearing);
        lastDesiredBearing = currentDesiredBearing;
    }

    sleep(200);
}

Important notes

1. Use one packet format everywhere

This is what makes the protocol reliable.

2. Do not mix strings and bytes

Strings like "WPT" or "NAV" are human-readable, but not ideal for a robust binary protocol.

3. Make payload lengths fixed

That lets the receiver know exactly how many bytes to read.

4. Scale floating-point values

For bearings, use:

  • 79.35 -> 7935
  • 85.00 -> 8500

5. ARC script should not assume bytes are immediately available

Always check UART.hardwareUartAvailable() before reading.


ARC configuration reminder

In ARC, make sure your Script skill is configured by using the kebab menu on the skill window and confirming:

  • the correct script language is selected
  • the correct UART index is used
  • the project has the EZB Connection skill added

If you want, I can also give you:

  1. a diagram of the packet flow
  2. a version with checksum/CRC added
  3. a more compact waypoint packet generator for ARC
  4. a matching Arduino parser that uses a state machine with checksum

If you want the next step, I’d recommend adding a 1-byte checksum so this protocol becomes much more resilient to noise on the UART line.


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.
#5  

Hi @Athena, Thank you for your previous post.  I am studying it, and as I do, it makes more sense to me.  Perhaps you could help me, in this teachable moment, by choosing a command and explain how the logic flows and the steps that follow to final execution?  In other words, once a command is initiated in the Loop, the path that it follows before the task is complete.

I would love it if you could do this for each Arduino sketch (TX + RX) and for the JavaScript script (providing an example of TX and RX).   Thank you very much.  Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#6  

Absolutely - that’s a great way to learn it.

What you want is the runtime path of each command:

  • who detects the event
  • who builds the packet
  • who sends it
  • who receives it
  • who parses it
  • what action happens next
  • when the task is considered complete

Below I’ll walk through one command flow at a time for:

  1. Arduino RX sketch
  2. Arduino TX sketch
  3. ARC JavaScript script
  4. Example TX and RX paths in both directions

1) Arduino RX sketch: command flow example

Let’s use the Waypoint Data command because it’s the most complete one.

Example command

  • ARC sends: 0xA5 0x06 ...payload...
  • Meaning: Waypoint Data

Logic flow

Step 1: ARC decides it’s time to send waypoint data

This usually happens when your ARC navigation logic has computed:

  • pathTotalDistance
  • combinedStepsDesired
  • oppositeDistance
  • bearingAlignment
  • reflectorBearing
  • sensorPrime
  • desiredBearing
  • actualBearing
  • trackDir

Step 2: ARC JavaScript builds the packet

It converts each number into bytes and places them into one packet.

Example shape:

[0xA5, 0x06, ...payload...]

Step 3: ARC sends the byte array over UART

Using:

UART.hardwareUartWrite(0, packet)

Step 4: Arduino RX sketch sees bytes arriving on Serial3

The Arduino loop runs constantly and checks:

  • is there a byte available?
  • is it the header byte 0xA5?
  • is the next byte a recognized command?

Step 5: Arduino parser enters packet state machine

The RX sketch should not assume the full packet arrives all at once.

Instead it should:

  1. wait for header
  2. wait for command byte
  3. wait for the exact payload length
  4. store bytes in order
  5. process only when complete

Step 6: Arduino processes the complete packet

When the full waypoint packet is received, the Arduino:

  • decodes the fields
  • stores them in variables
  • sets run = 1
  • sends an acknowledgement back to ARC

Step 7: Arduino sends NAV / ack equivalent

In the binary protocol, this would be:

  • header 0xA5
  • command 0x10

Step 8: Task is complete

At that point:

  • Arduino has the waypoint data
  • ARC has confirmation
  • forward motion can begin

2) Arduino TX sketch: command flow example

Now let’s use Total Steps Taken.

Example command

  • Arduino sends: 0xA5 0x11 ...4 bytes...
  • Meaning: total step count

Logic flow

Step 1: Arduino is in run == 1

That means the rover is actively moving forward and tracking.

Step 2: Step counts are being updated internally

The Arduino computes:

totalStepsTaken = abs(LF) + abs(LB) + abs(RF) + abs(RB);

Step 3: A condition triggers transmission

For example:

  • reached waypoint
  • stop command received
  • periodic status update
  • obstacle scan triggered

Step 4: Arduino builds a binary packet

It converts the 32-bit number into 4 bytes:

  • low byte first
  • then next byte
  • etc.

Step 5: Arduino writes bytes out serially

The TX sketch sends:

Serial3.write(0xA5);
Serial3.write(0x11);
Serial3.write(byte0);
Serial3.write(byte1);
Serial3.write(byte2);
Serial3.write(byte3);

Step 6: ARC receives the bytes in its loop

ARC checks UART.hardwareUartAvailable(0) and if enough bytes exist, reads them.

Step 7: ARC decodes the packet

ARC:

  • checks the header
  • checks the command
  • reads 4 bytes
  • reconstructs the integer

Step 8: ARC uses the data

ARC may:

  • display it
  • compare it to combinedStepsDesired
  • decide to stop navigation
  • stop scripts
  • log the result

Step 9: Task is complete

The number has been transmitted, received, and acted on.


3) ARC JavaScript script: command flow example

Let’s use Actual Bearing Update as an example of ARC Arduino transmission.

Example command

  • ARC sends: 0xA5 0x03 ...2 bytes...
  • Meaning: actual bearing update

Logic flow

Step 1: ARC detects a new actual bearing value

This might come from:

  • a servo position
  • a sensor reading
  • a computed heading update

Step 2: ARC compares it to the previous value

If the value changed, then ARC decides to send it.

Step 3: ARC scales the bearing

If the bearing is 79.35, ARC converts it to:

7935

Step 4: ARC packs the data

Using a helper like:

u16ToBytes()

Step 5: ARC sends the packet over UART

Example:

UART.hardwareUartWrite(0, [0xA5, 0x03, lowByte, highByte])

Step 6: Arduino RX sketch receives it

The Arduino parser:

  • finds header 0xA5
  • reads command 0x03
  • reads the 2-byte payload
  • decodes the bearing

Step 7: Arduino updates motor control logic

The Arduino can now compare:

  • desired bearing
  • actual bearing

Then adjust PWM accordingly.

Step 8: Task is complete

The Arduino has the updated actual bearing and can continue motor correction.


4) Example TX and RX flow in both directions

ARC Arduino example: Stop Motors

Flow

  1. ARC detects emergency stop condition
  2. ARC sends packet:
    [0xA5, 0x01]
    
  3. Arduino RX sees the header and command
  4. Arduino immediately stops motors
  5. Arduino may send back total steps taken
  6. ARC receives the response and ends the task

Arduino ARC example: Desired Bearing Update

Flow

  1. Arduino computes a new desired bearing
  2. Arduino converts it to scaled integer form
  3. Arduino sends:
    [0xA5, 0x12, lowByte, highByte]
    
  4. ARC receives the packet
  5. ARC converts the bytes back into a number
  6. ARC prints the bearing for operator monitoring
  7. Task is complete

5) How the logic should be thought about

A UART command should always follow this mental model:

Sender side

  • Decide to send
  • Build bytes
  • Send packet
  • Optionally wait for confirmation

Receiver side

  • Detect header
  • Detect command
  • Read exact payload size
  • Decode values
  • Perform action
  • Optionally reply

That is the whole protocol loop.


6) Suggested command lifecycle

For reliability, each command should have this lifecycle:

Phase Meaning
Trigger Something happens in logic
Build Packet bytes are assembled
Send Bytes go out over UART
Receive Other side reads bytes
Parse Packet is decoded
Act Robot performs the task
Confirm Optional acknowledgement
Complete State updates and loop continues

7) Very important implementation note

Your current code mixes:

  • ASCII text commands
  • byte arrays
  • partial packet reads
  • occasional single-byte reads

That can work sometimes, but it becomes fragile.

For a reliable system, the RX side should always parse bytes in a state machine, not by assuming:

  • "if 5 bytes are available, this must be my packet"

That assumption can fail if:

  • bytes arrive in chunks
  • packets get back-to-back
  • line timing changes

8) ARC scripting example: TX and RX in one loop

Here’s the simplified logic:

while(true) {
    // TX example
    if (shouldSendStop) {
        UART.hardwareUartWrite(0, [0xA5, 0x01]);
        shouldSendStop = false;
    }

    // RX example
    var available = UART.hardwareUartAvailable(0);
    if (available >= 2) {
        var header = UART.hardwareUartRead(0, 1)[0];
        var cmd = UART.hardwareUartRead(0, 1)[0];

        if (header == 0xA5 && cmd == 0x11) {
            // read remaining bytes for total steps
        }
    }

    sleep(20);
}

If you want, I can do one more step and give you:

  1. a full command-by-command flowchart
  2. a state machine diagram
  3. a corrected Arduino RX parser
  4. a corrected ARC JavaScript loop
  5. or a line-by-line explanation of your original code using this protocol

If you'd like, I’d recommend starting with the Arduino RX state machine, because that’s the foundation of reliability.


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.
#7  

Hi @Athena, I can see that we will need to rewrite both the Arduino and ARC codes.  Can we start with the full command by command flowchart and the Arduino RX state machine?  As you do this, please explain to me as a beginner so that I can follow.  The previous post did a very good job with the explanations.   Thanks. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#8  

Absolutely, Jack - starting with the command-by-command flowchart and the Arduino RX state machine is the right move.

Below I’ll keep this beginner-friendly and focused on how the data moves, what each side is responsible for, and why the Arduino RX parser must be a state machine for reliability.


1) Command-by-command flowchart

Big picture

You have two directions of UART communication:

  • ARC Arduino
  • Arduino ARC

And each direction has specific jobs.


A. ARC Arduino command flow

These are the commands ARC sends to Arduino:

Command Purpose When it happens
STOP Stop all motors Emergency stop, waypoint complete, safety stop
MISSED IR Tell Arduino the reflector was lost When ARC loses the IR target
ACTUAL BEARING Send updated heading When ARC has a new bearing value
RIGHT PIVOT Tell Arduino to pivot right During alignment
LEFT PIVOT Tell Arduino to pivot left During alignment
WAYPOINT DATA Send the full navigation packet At the start of a leg
OBSTACLE SCAN COMPLETE Resume normal movement after scan After obstacle scan ends

Flowchart: ARC Arduino

[ARC logic detects event]
        |
        v
[Choose command]
        |
        v
[Build packet bytes]
        |
        v
[Send packet over UART]
        |
        v
[Arduino RX reads bytes]
        |
        v
[Arduino parser validates header + command]
        |
        v
[Arduino reads payload]
        |
        v
[Arduino performs action]
        |
        v
[Optional Arduino response back to ARC]

B. Arduino ARC command flow

These are the messages Arduino sends back:

Command Purpose When it happens
NAV ACK Confirms waypoint packet received Right after WAYPOINT DATA is accepted
TOTAL STEPS TAKEN Sends step count At pivot completion, waypoint completion, stop, etc.
DESIRED BEARING UPDATE Sends updated desired bearing When desired bearing changes
OBSTACLE SCAN REQUEST Tells ARC a scan state has begun During scan logic

Flowchart: Arduino ARC

[Arduino logic detects event]
        |
        v
[Choose response command]
        |
        v
[Build packet bytes]
        |
        v
[Send packet over Serial3]
        |
        v
[ARC RX loop sees bytes available]
        |
        v
[ARC parser validates header + command]
        |
        v
[ARC reads payload]
        |
        v
[ARC updates variables / prints info / changes behavior]

2) Why the Arduino RX side needs a state machine

This is the most important beginner concept.

UART is a stream of bytes, not a "message box."

That means when ARC sends a packet, Arduino might receive:

  • all bytes at once, or
  • part of a packet now and the rest later, or
  • two packets back-to-back

So this is not reliable:

if (Serial3.available() >= 5) {
    // assume packet is here
}

Instead, the Arduino must do this:

  1. wait for a header byte
  2. wait for a command byte
  3. determine how many payload bytes to expect
  4. collect them one by one
  5. only when the full packet has arrived, process it

That is called a state machine.


3) Arduino RX state machine concept

States

The parser moves through these states:

State Meaning
WAIT_HEADER Looking for the start byte
WAIT_CMD Header found, now waiting for command byte
WAIT_PAYLOAD Command found, now collecting the expected payload

Example packet

Let’s say ARC sends:

[0xA5, 0x06, ...payload...]

That means:

  • 0xA5 = packet header
  • 0x06 = waypoint data command
  • payload = the navigation values

How the state machine behaves

Start
  |
  v
WAIT_HEADER
  |
  | sees 0xA5
  v
WAIT_CMD
  |
  | sees 0x06
  v
WAIT_PAYLOAD
  |
  | reads exact number of payload bytes
  v
Process packet
  |
  v
Return to WAIT_HEADER

4) Arduino RX state machine sketch

Below is a clean starting point for your Arduino RX sketch.

This sketch:

  • reads from Serial3
  • parses packets safely
  • supports STOP, MISSED IR, ACTUAL BEARING, RIGHT PIVOT, LEFT PIVOT, and WAYPOINT DATA
  • shows where you would plug in your motor logic

Arduino RX sketch

#include <Arduino.h>

static const uint8_t UART_HEADER = 0xA5;

// ARC -> Arduino commands
static const uint8_t CMD_STOP_MOTORS         = 0x01;
static const uint8_t CMD_MISSED_IR           = 0x02;
static const uint8_t CMD_ACTUAL_BEARING      = 0x03;
static const uint8_t CMD_RIGHT_PIVOT         = 0x04;
static const uint8_t CMD_LEFT_PIVOT          = 0x05;
static const uint8_t CMD_WAYPOINT_DATA       = 0x06;
static const uint8_t CMD_OBS_SCAN_COMPLETE   = 0x07;

// Packet parser states
enum ParserState {
  WAIT_HEADER,
  WAIT_CMD,
  WAIT_PAYLOAD
};

ParserState state = WAIT_HEADER;
uint8_t currentCmd = 0;
uint8_t payload[32];
uint8_t payloadIndex = 0;
uint8_t expectedLength = 0;

// Example navigation variables
uint32_t pathTotalDistance = 0;
uint32_t combinedStepsDesired = 0;
uint32_t oppositeDistance = 0;
uint8_t bearingAlignment = 0;
uint16_t reflectorBearing100 = 0;
uint8_t sensorPrime = 0;
uint16_t desiredBearing100 = 0;
uint16_t actualBearing100 = 0;
uint8_t trackDir = 0;

uint16_t readUInt16LE(const uint8_t* data) {
  return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}

uint32_t readUInt32LE(const uint8_t* data) {
  return (uint32_t)data[0]
       | ((uint32_t)data[1] << 8)
       | ((uint32_t)data[2] << 16)
       | ((uint32_t)data[3] << 24);
}

uint8_t getPayloadLength(uint8_t cmd) {
  switch (cmd) {
    case CMD_STOP_MOTORS:
    case CMD_MISSED_IR:
    case CMD_OBS_SCAN_COMPLETE:
      return 0;

    case CMD_ACTUAL_BEARING:
    case CMD_RIGHT_PIVOT:
    case CMD_LEFT_PIVOT:
      return 2;

    case CMD_WAYPOINT_DATA:
      return 21; // payload only, not including header + cmd

    default:
      return 0xFF; // invalid command
  }
}

void stopMotors() {
  Serial.println("Stopping motors");
  // Put your motor stop code here
}

void handleWaypointPacket() {
  pathTotalDistance     = readUInt32LE(&payload[0]);
  combinedStepsDesired  = readUInt32LE(&payload[4]);
  oppositeDistance      = readUInt32LE(&payload[8]);
  bearingAlignment      = payload[12];
  reflectorBearing100   = readUInt16LE(&payload[13]);
  sensorPrime           = payload[15];
  desiredBearing100     = readUInt16LE(&payload[16]);
  actualBearing100      = readUInt16LE(&payload[18]);
  trackDir              = payload[20];

  Serial.println("Waypoint packet received");
  Serial.println("Sending NAV ACK");

  // Example: begin movement setup
  // run = 1;

  // Send acknowledgement back to ARC here if desired
  // Example packet: [0xA5, 0x10]
  Serial3.write(UART_HEADER);
  Serial3.write(0x10);
}

void handlePacket(uint8_t cmd, const uint8_t* data, uint8_t len) {
  switch (cmd) {
    case CMD_STOP_MOTORS:
      Serial.println("Received STOP");
      stopMotors();
      break;

    case CMD_MISSED_IR:
      Serial.println("Received MISSED IR");
      // Set your missed flag / adjust control logic
      break;

    case CMD_ACTUAL_BEARING: {
      uint16_t bearing = readUInt16LE(data);
      Serial.print("Received Actual Bearing x100: ");
      Serial.println(bearing);
      // Store actual bearing here
      break;
    }

    case CMD_RIGHT_PIVOT: {
      uint16_t steps = readUInt16LE(data);
      Serial.print("Received Right Pivot steps: ");
      Serial.println(steps);
      // Start right pivot logic here
      break;
    }

    case CMD_LEFT_PIVOT: {
      uint16_t steps = readUInt16LE(data);
      Serial.print("Received Left Pivot steps: ");
      Serial.println(steps);
      // Start left pivot logic here
      break;
    }

    case CMD_WAYPOINT_DATA:
      handleWaypointPacket();
      break;

    case CMD_OBS_SCAN_COMPLETE:
      Serial.println("Obstacle scan complete");
      // Resume normal logic here
      break;

    default:
      Serial.print("Unknown command: ");
      Serial.println(cmd, HEX);
      break;
  }
}

void processIncomingByte(uint8_t b) {
  switch (state) {
    case WAIT_HEADER:
      if (b == UART_HEADER) {
        state = WAIT_CMD;
      }
      break;

    case WAIT_CMD:
      currentCmd = b;
      expectedLength = getPayloadLength(currentCmd);

      if (expectedLength == 0xFF) {
        state = WAIT_HEADER;
      } else if (expectedLength == 0) {
        handlePacket(currentCmd, nullptr, 0);
        state = WAIT_HEADER;
      } else {
        payloadIndex = 0;
        state = WAIT_PAYLOAD;
      }
      break;

    case WAIT_PAYLOAD:
      payload[payloadIndex++] = b;

      if (payloadIndex >= expectedLength) {
        handlePacket(currentCmd, payload, expectedLength);
        state = WAIT_HEADER;
      }
      break;
  }
}

void setup() {
  Serial.begin(115200);
  Serial3.begin(115200);
  Serial.println("Arduino RX ready");
}

void loop() {
  while (Serial3.available() > 0) {
    uint8_t b = (uint8_t)Serial3.read();
    processIncomingByte(b);
  }
}

5) Beginner explanation of the Arduino RX logic

What happens first?

The Arduino runs loop() over and over.

Inside that loop:

  • it checks Serial3.available()
  • if a byte exists, it reads one byte
  • that byte is sent to processIncomingByte()

Why only one byte at a time?

Because UART is a stream. Reading byte-by-byte lets the Arduino stay synchronized.

Why does the header matter?

The header byte 0xA5 says:

"A new packet starts here."

If the Arduino gets out of sync, it can ignore garbage until it sees 0xA5 again.

Why does the command matter?

The command tells Arduino:

  • what kind of packet this is
  • how many payload bytes to expect
  • what action to take after parsing

Why not process immediately?

Because the packet may not be complete yet.

If you process too early, the data will be wrong.


6) What happens after the packet is complete?

Once the Arduino receives the full packet:

  • it decodes the fields
  • stores the values in variables
  • starts the corresponding motor or navigation action
  • optionally sends a response back to ARC

For example, with WAYPOINT DATA:

  1. Arduino receives the full packet
  2. Arduino stores all navigation values
  3. Arduino sends NAV ACK
  4. Arduino begins the movement phase

That is the full command path.


7) Next step: Arduino TX sketch

The TX side is simpler than the RX side because it only sends data.

It will:

  • build packets like TOTAL STEPS TAKEN
  • send DESIRED BEARING UPDATE
  • optionally send NAV ACK

In the next message, I can write the Arduino TX sketch and the ARC JavaScript script in the same style, with the same beginner-friendly walkthrough.

If you want, I can continue with:

  1. Arduino TX sketch
  2. ARC JavaScript script
  3. a packet-by-packet walkthrough for each command

If you'd like, I recommend we do the Arduino TX sketch next, then the ARC JavaScript loop.


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.
 
📚 This thread has been preserved as a helpful resource!

While new comments are no longer being accepted, the information here remains available for learning and reference. Check the most recent comment for additional details.