Wowwee Rovio icon Wowwee Rovio Movement panel integrating WowWee Rovio with Synthiam ARC for remote drive, live camera, audio and docking control. Under development. 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

Take control of your robot's destiny by subscribing to Synthiam ARC Pro, and watch it evolve into a versatile and responsive machine.

#9  

Hi @Athena, So, it appears that in the State Machine Concept, each step (Header, Command, Payload, etc.) has been preprogrammed into the UART receiver code so that it recognizes first the Command which triggers it to look for the appropriate remaining Command and Payload.  Incredible, logical.   I am thrilled to be at this step in my robot development.  My present code, a real mishmash of bits, proved my robot concept but hardly adequate to move forward.  I am unbelievably excited to learn how to transform it into properly structured logic.   Can you please continue with the Arduino TX sketch and then the ARC JavaScript loop? Thanks, Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#10   — Edited

1) Arduino TX Sketch

What this sketch does

This sketch is the Arduino sending data back to ARC.

It will send packets like:

  • NAV ACK
  • TOTAL STEPS TAKEN
  • DESIRED BEARING UPDATE
  • OBSTACLE SCAN REQUEST

2) Arduino TX Flowchart

[Arduino decides it needs to send a message]
        |
        v
[Choose the command ID]
        |
        v
[Convert numbers to bytes if needed]
        |
        v
[Build packet: Header + Command + Payload]
        |
        v
[Write bytes to Serial3]
        |
        v
[ARC receives and parses packet]
        |
        v
[ARC updates state / prints / reacts]

3) Arduino TX Sketch

Clean TX template

#include 

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 sendHeaderAndCmd(uint8_t cmd) {

  Serial3.write(UART_HEADER);
  Serial3.write(cmd);
}

void sendUInt16Packet(uint8_t cmd, uint16_t value) {

  Serial3.write(UART_HEADER);
  Serial3.write(cmd);

  Serial3.write((uint8_t)(value & 0xFF));           // low byte
  Serial3.write((uint8_t)((value >> 8) & 0xFF));    // high byte
}

void sendUInt32Packet(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));
}

void sendNavAck() {

  sendHeaderAndCmd(CMD_NAV_ACK);

  Serial.println("Sent NAV ACK");
}

void sendTotalSteps(uint32_t steps) {

  sendUInt32Packet(CMD_TOTAL_STEPS, steps);

  Serial.print("Sent total steps: ");
  Serial.println(steps);
}

void sendDesiredBearing(uint16_t bearing100) {

  sendUInt16Packet(CMD_DESIRED_BEARING, bearing100);

  Serial.print("Sent desired bearing x100: ");
  Serial.println(bearing100);
}

void sendObstacleScanRequest() {

  sendHeaderAndCmd(CMD_OBS_SCAN_REQUEST);

  Serial.println("Sent obstacle scan request");
}

void setup() {

  Serial.begin(115200);
  Serial3.begin(115200);

  Serial.println("Arduino TX ready");
}

void loop() {

  // Example test behavior only
  // In your real project, these would be called when events occur

  /*
  sendNavAck();
  delay(1000);

  sendTotalSteps(123456);
  delay(1000);

  sendDesiredBearing(7935); // 79.35 degrees
  delay(1000);

  sendObstacleScanRequest();
  delay(1000);
  */
}

4) Beginner Explanation of Arduino TX

What this code is doing

Think of the Arduino as saying:

"I need to tell ARC something."

It builds a packet like this:

  1. Header byte 0xA5
  2. Command byte
  3. Payload bytes (if needed)

Why helper functions exist

Each packet size is different:

  • NAV ACK = 0 bytes
  • TOTAL STEPS = 4 bytes
  • DESIRED BEARING = 2 bytes

Helpers prevent mistakes and keep things clean.


5) Example TX Flow (TOTAL STEPS)

Step 1

uint32_t steps = 123456;

Step 2

Arduino decides to report it.

Step 3

Packet:

[0xA5, 0x11, lowByte, byte1, byte2, highByte]

Step 4

Serial3.write(...)

Step 5

ARC reads:

  • header
  • command
  • payload

Step 6

ARC reacts:

  • print
  • compare
  • update state

6) ARC JavaScript Loop

What it does

  • Sends commands to Arduino
  • Receives responses
  • Runs continuously

7) ARC Flow

[ARC loop running]
        |
        v
[Check if event to send]
        |
        v
[Build packet]
        |
        v
[UART.hardwareUartWrite()]
        |
        v
[Check incoming bytes]
        |
        v
[Read + decode packet]
        |
        v
[Update state]
        |
        v
[Sleep]
        |
        v
[Repeat]

8) ARC JavaScript Code

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);

  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)));

  payload.push(sensorPrime & 0xFF);

  payload = payload.concat(u16ToBytes(Math.round(desiredBearing * 100)));
  payload = payload.concat(u16ToBytes(Math.round(actualBearing * 100)));

  payload.push(trackDir & 0xFF);

  sendPacket(CMD_WAYPOINT_DATA, payload);
}

function readPacket() {

  var available = UART.hardwareUartAvailable(UART_INDEX);

  if (available < 2) {

    return null;
  }

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

  if (header != UART_HEADER) {

    return null;
  }

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

  if (cmd == RSP_NAV_ACK) {

    print("Arduino says NAV ACK");
    return;
  }

  if (cmd == RSP_TOTAL_STEPS) {

    if (UART.hardwareUartAvailable(UART_INDEX) < 4) {

      return null;
    }

    var data = UART.hardwareUartRead(UART_INDEX, 4);

    var steps =
      data[0] |
      (data[1] << 8) |
      (data[2] << 16) |
      (data[3] << 24);

    print("Total steps received: " + steps);
  }
}

9) Key Concept

The loop does two jobs:

  • TX: send when needed
  • RX: constantly listen

10) Important Tip

Even inside one loop, think in 3 layers:

  1. Event logic
  2. Packet building
  3. Packet parsing

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

Hi @Athena, Two requests.

  1. Can you combine the Arduino TX and RX sketches into one sketch?  In the Loop, I will sort out what I want done based upon whether RUN = 1 or RUN = 0.

2.  In the JavaScript TX/RX code, can you expound on the Loop three layers you refer to? (event logic, Packet building, Packet parsing)? Can you provide an example? Thank you. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#12  

Absolutely,Jack - both requests make sense.

1) Combine Arduino TX + RX into one sketch

Yes. That is actually the cleaner way to do it for your project, because:

  • the Arduino can receive commands from ARC
  • the Arduino can send responses back to ARC
  • RUN == 0 and RUN == 1 can both be handled in one loop()
  • you can keep all protocol logic in one place

The key idea is:

  • RX side: always listen for incoming UART bytes and parse them with the state machine
  • TX side: send packets only when some event occurs
  • main loop: decides behavior based on RUN

So the Arduino becomes one unified communication node.


2) The three layers in the ARC JavaScript loop

Think of the ARC loop as three jobs happening repeatedly:

Layer 1: Event logic

This asks:

  • Did something happen?
  • Do I need to send a packet?
  • Do I need to react to something I received?

Examples:

  • $obsDet == 1
  • $missedIR == 1
  • $actualBearing changed
  • start of waypoint navigation

This layer is the decision layer.


Layer 2: Packet building

If the event logic says "yes, send something," then this layer:

  • chooses the command byte
  • converts values into bytes
  • builds the final byte array

Example:

  • actual bearing 79.35
  • scale to 7935
  • convert to bytes:
    • low byte
    • high byte

This layer is the packing layer.


Layer 3: Packet parsing

This layer looks at incoming UART data and asks:

  • is a header byte available?
  • what command did I receive?
  • do I have all required payload bytes?
  • can I decode it now?

This layer is the reception layer.


Combined Arduino sketch

Below is a single Arduino sketch that includes both TX and RX behavior, with a parser state machine.

#include <Arduino.h>

// --------------------
// UART Protocol
// --------------------
static const uint8_t UART_HEADER = 0xA5;

// ARC -> Arduino
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;

// Arduino -> ARC
static const uint8_t RSP_NAV_ACK             = 0x10;
static const uint8_t RSP_TOTAL_STEPS         = 0x11;
static const uint8_t RSP_DESIRED_BEARING     = 0x12;
static const uint8_t RSP_OBS_SCAN_REQUEST    = 0x13;

// --------------------
// Run state
// --------------------
int RUN = 0;

// --------------------
// Parser state machine
// --------------------
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 totalStepsTaken = 0;
uint16_t desiredBearing100 = 0;
uint16_t actualBearing100 = 0;

// Waypoint packet fields
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 waypointDesiredBearing100 = 0;
uint16_t waypointActualBearing100 = 0;
uint8_t trackDir = 0;

// --------------------
// Helpers
// --------------------
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);
}

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

void sendUInt16Packet(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 sendUInt32Packet(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));
}

// --------------------
// TX functions
// --------------------
void sendNavAck() {
  sendHeaderAndCmd(RSP_NAV_ACK);
}

void sendTotalSteps(uint32_t steps) {
  sendUInt32Packet(RSP_TOTAL_STEPS, steps);
}

void sendDesiredBearing(uint16_t bearing100) {
  sendUInt16Packet(RSP_DESIRED_BEARING, bearing100);
}

void sendObstacleScanRequest() {
  sendHeaderAndCmd(RSP_OBS_SCAN_REQUEST);
}

// --------------------
// RX packet handlers
// --------------------
void handleWaypointPacket(const uint8_t* data, uint8_t len) {
  if (len < 23) return;

  pathTotalDistance        = readUInt32LE(&data[0]);
  combinedStepsDesired     = readUInt32LE(&data[4]);
  oppositeDistance         = readUInt32LE(&data[8]);
  bearingAlignment         = data[12];
  reflectorBearing100      = readUInt16LE(&data[13]);
  sensorPrime              = data[15];
  waypointDesiredBearing100= readUInt16LE(&data[16]);
  waypointActualBearing100 = readUInt16LE(&data[18]);
  trackDir                 = data[20];

  RUN = 1;

  Serial.print("Waypoint received. PathTotalDistance=");
  Serial.println(pathTotalDistance);
  Serial.print("CombinedStepsDesired=");
  Serial.println(combinedStepsDesired);
  Serial.print("OppositeDistance=");
  Serial.println(oppositeDistance);
  Serial.print("BearingAlignment=");
  Serial.println(bearingAlignment);
  Serial.print("ReflectorBearing100=");
  Serial.println(reflectorBearing100);
  Serial.print("SensorPrime=");
  Serial.println(sensorPrime);
  Serial.print("DesiredBearing100=");
  Serial.println(waypointDesiredBearing100);
  Serial.print("ActualBearing100=");
  Serial.println(waypointActualBearing100);
  Serial.print("TrackDir=");
  Serial.println(trackDir);

  sendNavAck();
}

void handlePacket(uint8_t cmd, const uint8_t* data, uint8_t len) {
  switch (cmd) {
    case CMD_STOP_MOTORS:
      Serial.println("CMD_STOP_MOTORS received");
      RUN = 0;
      // stopMotors();
      sendTotalSteps(totalStepsTaken);
      break;

    case CMD_MISSED_IR:
      Serial.println("CMD_MISSED_IR received");
      // missedIR = true;
      break;

    case CMD_ACTUAL_BEARING:
      if (len >= 2) {
        actualBearing100 = readUInt16LE(data);
        Serial.print("Actual bearing received x100 = ");
        Serial.println(actualBearing100);
      }
      break;

    case CMD_RIGHT_PIVOT:
      if (len >= 2) {
        uint16_t steps = readUInt16LE(data);
        Serial.print("Right pivot steps = ");
        Serial.println(steps);
        // pivotRight(steps);
      }
      break;

    case CMD_LEFT_PIVOT:
      if (len >= 2) {
        uint16_t steps = readUInt16LE(data);
        Serial.print("Left pivot steps = ");
        Serial.println(steps);
        // pivotLeft(steps);
      }
      break;

    case CMD_WAYPOINT_DATA:
      handleWaypointPacket(data, len);
      break;

    case CMD_OBS_SCAN_COMPLETE:
      Serial.println("Obstacle scan complete received");
      // resume normal tracking
      break;

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

uint8_t getExpectedLength(uint8_t cmd) {
  switch (cmd) {
    case CMD_STOP_MOTORS:       return 0;
    case CMD_MISSED_IR:         return 0;
    case CMD_ACTUAL_BEARING:    return 2;
    case CMD_RIGHT_PIVOT:       return 2;
    case CMD_LEFT_PIVOT:        return 2;
    case CMD_WAYPOINT_DATA:     return 23;
    case CMD_OBS_SCAN_COMPLETE: return 0;
    default:                    return 0xFF;
  }
}

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

    case WAIT_CMD:
      currentCmd = b;
      expectedLength = getExpectedLength(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 readIncomingUart() {
  while (Serial3.available() > 0) {
    uint8_t b = (uint8_t)Serial3.read();
    processIncomingByte(b);
  }
}

// --------------------
// Setup / Loop
// --------------------
void setup() {
  Serial.begin(115200);
  Serial3.begin(115200);
  Serial.println("Combined Arduino UART sketch ready");
}

void loop() {
  // Always read incoming data
  readIncomingUart();

  if (RUN == 0) {
    // Idle / alignment / pre-navigation logic
    // Example: if a waypoint is received, RUN becomes 1 in handleWaypointPacket()
  } 
  else if (RUN == 1) {
    // Forward navigation logic
    // Update total steps
    totalStepsTaken = 123456; // replace with real step calculation

    // Example periodic reporting
    // sendTotalSteps(totalStepsTaken);

    // Example: if desired bearing changes
    // sendDesiredBearing(desiredBearing100);
  }

  // Add a small delay if needed
  // delay(1);
}

ARC JavaScript: the three-layer loop

Here’s the beginner-friendly way to think about it:

  • Event logic decides whether to send
  • Packet building creates the bytes
  • Packet parsing reads incoming bytes

Below is a single script using that structure.

var UART_INDEX = 0;
var UART_HEADER = 0xA5;

// ARC -> Arduino
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;

// Arduino -> ARC
var RSP_NAV_ACK           = 0x10;
var RSP_TOTAL_STEPS       = 0x11;
var RSP_DESIRED_BEARING   = 0x12;
var RSP_OBS_SCAN_REQUEST  = 0x13;

// Example state
var lastActualBearing = -1;
var lastDesiredBearing = -1;

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 bearing100 = Math.round(bearingDegrees * 100);
    sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
}

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)));
    payload.push(sensorPrime & 0xFF);
    payload = payload.concat(u16ToBytes(Math.round(desiredBearing * 100)));
    payload = payload.concat(u16ToBytes(Math.round(actualBearing * 100)));
    payload.push(trackDir & 0xFF);

    sendPacket(CMD_WAYPOINT_DATA, payload);
}

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

    var header = UART.hardwareUartRead(UART_INDEX, 1)[0];
    if (header != UART_HEADER) return;

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

    if (cmd == RSP_NAV_ACK) {
        print("ARC received NAV ACK");
        return;
    }

    if (cmd == RSP_TOTAL_STEPS) {
        if (UART.hardwareUartAvailable(UART_INDEX) < 4) return;

        var data = UART.hardwareUartRead(UART_INDEX, 4);
        var steps = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
        print("Total steps received: " + steps);
        return;
    }

    if (cmd == RSP_DESIRED_BEARING) {
        if (UART.hardwareUartAvailable(UART_INDEX) < 2) return;

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

    if (cmd == RSP_OBS_SCAN_REQUEST) {
        print("Arduino requested obstacle scan");
        return;
    }
}

while (true) {
    // -----------------------
    // Layer 1: Event logic
    // -----------------------
    if (getVar("$obsDet") == 1) {
        sendStopMotors();
        setVar("$obsDet", 0);
    }

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

    var currentActualBearing = getVar("$actualBearing", -1);
    if (currentActualBearing != lastActualBearing && currentActualBearing != -1) {
        sendActualBearing(currentActualBearing);
        lastActualBearing = currentActualBearing;
    }

    var currentDesiredBearing = getVar("$desiredBearing", -1);
    if (currentDesiredBearing != lastDesiredBearing && currentDesiredBearing != -1) {
        // If you want ARC to report desired bearing immediately:
        // send it as actual-bearing-style packet if your protocol uses that command
        lastDesiredBearing = currentDesiredBearing;
    }

    // -----------------------
    // Layer 2: Packet building
    // -----------------------
    // This already happens inside sendStopMotors(), sendActualBearing(), sendWaypointData(), etc.

    // Example: send waypoint once when some project variable says to start
    if (getVar("$sendWaypointNow") == 1) {
        sendWaypointData(120000, 84000, 24000, 0, 97, 4, 79.35, 79.00, 0);
        setVar("$sendWaypointNow", 0);
    }

    // -----------------------
    // Layer 3: Packet parsing
    // -----------------------
    readIncomingPacket();

    sleep(20);
}

Example of the three layers in action

Example: ARC sends STOP

Layer 1: Event logic

ARC sees:

  • $obsDet == 1

So it decides to stop the motors.

Layer 2: Packet building

It builds:

[0xA5, 0x01]

Layer 3: Packet parsing

Arduino receives:

  • header 0xA5
  • command 0x01

Then Arduino:

  • stops motors
  • may send total steps back
  • returns to listening

Beginner takeaway

The best way to think about it is:

ARC side

  • decide
  • build
  • send
  • listen

Arduino side

  • listen
  • detect
  • decode
  • act
  • reply

If you want, I can next give you:

  1. a single-page visual flowchart for the whole system, or
  2. a fully corrected Arduino parser with your real RUN = 0 / RUN = 1 logic, or
  3. a rewritten ARC loop using your actual project variables like $obsDet, $missedIR, $actualBearing, and $obsScanEnd.

If you’d like, I recommend the next step be the rewritten Arduino sketch with your RUN logic inserted, because that will make the architecture feel very real.


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

Hi @Athena, Can you please send a flow chart for the whole system?  This way I can confirm it meets my objectives before writing code. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#14  

Absolutely,Jack - here’s a whole-system flowchart for the UART architecture before you write code.

I’m keeping it at the system level first so you can confirm the logic matches your rover objectives.


Whole-System UART Flowchart

1) High-level architecture

+-------------------+        UART        +----------------------+
| ARC JavaScript    | <----------------> | Arduino Mega         |
| (runs on EZ-B v4) |                    | (stepper + nav logic) |
+-------------------+                    +----------------------+
         |                                           |
         |                                           |
         v                                           v
  Reads sensors / variables                  Controls motors
  Decides what to send                       Computes bearings
  Builds packets                             Sends status packets
  Parses Arduino replies                     Parses ARC commands

2) Overall control loop

                         +----------------------+
                         |        START         |
                         +----------------------+
                                   |
                                   v
                    +-------------------------------+
                    | ARC and Arduino are powered on |
                    +-------------------------------+
                                   |
                                   v
            +------------------------------------------------+
            | ARC JavaScript loop begins running continuously |
            +------------------------------------------------+
                                   |
                                   v
            +----------------------------------------------+
            | Arduino loop begins running continuously      |
            | RX parser always listens for UART bytes      |
            +----------------------------------------------+
                                   |
                                   v
                 +-----------------------------------+
                 | Which side has something to send? |
                 +-----------------------------------+
                          /                     \
                         /                       \
                        v                         v
        +---------------------------+   +---------------------------+
        | ARC sends command to      |   | Arduino sends response to |
        | Arduino when an event     |   | ARC when a status/event   |
        | happens                    |   | changes                  |
        +---------------------------+   +---------------------------+
                        \                         /
                         \                       /
                          v                     v
                 +-----------------------------------+
                 | Receiver parses packet safely     |
                 +-----------------------------------+
                                   |
                                   v
                     +------------------------------+
                     | Receiver performs the action |
                     +------------------------------+
                                   |
                                   v
                 +-----------------------------------+
                 | Optional response back the other   |
                 | way for confirmation/status       |
                 +-----------------------------------+
                                   |
                                   v
                         +----------------------+
                         |      LOOP AGAIN      |
                         +----------------------+

3) ARC Arduino flowchart

This is the direction for:

  • STOP
  • MISSED IR
  • ACTUAL BEARING
  • RIGHT PIVOT
  • LEFT PIVOT
  • WAYPOINT DATA
  • OBSTACLE SCAN COMPLETE
[ARC detects event]
        |
        v
[Choose command]
        |
        v
[Convert values to bytes]
        |
        v
[Build packet:
Header + Command + Payload]
        |
        v
[Send packet over UART]
        |
        v
[Arduino RX reads bytes]
        |
        v
[Arduino parser waits for:
Header -> Command -> Payload]
        |
        v
[Arduino decodes packet]
        |
        v
[Arduino updates motion/nav state]
        |
        v
[Arduino may send response back]

4) Arduino ARC flowchart

This is the direction for:

  • NAV ACK
  • TOTAL STEPS TAKEN
  • DESIRED BEARING UPDATE
  • OBSTACLE SCAN REQUEST
[Arduino detects internal event]
        |
        v
[Choose response command]
        |
        v
[Convert data to bytes if needed]
        |
        v
[Build packet:
Header + Command + Payload]
        |
        v
[Send packet over Serial3]
        |
        v
[ARC UART loop sees bytes available]
        |
        v
[ARC reads header]
        |
        v
[ARC reads command]
        |
        v
[ARC reads exact payload length]
        |
        v
[ARC decodes packet]
        |
        v
[ARC prints, logs, or changes state]

5) Example command path: WAYPOINT DATA

This is the best "big" example because it shows the full system working.

ARC decides a new waypoint is ready
        |
        v
ARC builds WPT packet
        |
        v
ARC sends packet to Arduino
        |
        v
Arduino RX parser receives bytes
        |
        v
Arduino recognizes:
Header + WPT command + 23-byte payload
        |
        v
Arduino stores:
distance, steps, bearings, direction
        |
        v
Arduino confirms with NAV ACK
        |
        v
ARC receives NAV ACK
        |
        v
ARC starts the navigation script
        |
        v
Arduino begins RUN = 1 movement logic

6) Example command path: TOTAL STEPS TAKEN

Arduino reaches a condition:
- waypoint complete
- pivot complete
- stop command received
        |
        v
Arduino computes total steps
        |
        v
Arduino builds:
Header + TOTAL STEPS command + 4-byte value
        |
        v
Arduino sends packet to ARC
        |
        v
ARC receives bytes
        |
        v
ARC parser reads packet safely
        |
        v
ARC reconstructs the 32-bit step count
        |
        v
ARC uses the value:
- display
- stop
- transition to next state

7) Example command path: ACTUAL BEARING

ARC gets a new actual bearing value
        |
        v
ARC compares it to previous value
        |
        v
If changed, ARC scales it x100
        |
        v
ARC builds packet:
Header + ACTUAL BEARING + 2-byte value
        |
        v
ARC sends to Arduino
        |
        v
Arduino parses packet
        |
        v
Arduino updates PWM correction logic
        |
        v
Motor correction continues

8) What RUN = 0 and RUN = 1 mean in the system

RUN = 0

This is the setup / alignment / pre-move phase.

ARC may send:
- pivot commands
- waypoint data
- stop
- bearing updates

Arduino may:
- wait for data
- pivot wheels
- confirm with NAV ACK

RUN = 1

This is the forward navigation phase.

Arduino may:
- count steps
- calculate desired bearing
- adjust PWM
- send total steps or desired bearing updates

ARC may:
- monitor status
- send missed IR
- send actual bearing updates
- stop motion if needed

9) The simplest "big picture" summary

ARC decides -> builds packet -> sends UART
Arduino receives -> parses packet -> acts
Arduino decides -> builds packet -> sends UART
ARC receives -> parses packet -> reacts

That is the entire system in one sentence.


10) Does this match your objective?

Based on what you described, yes - this flow supports your objective:

  • reliable UART communication
  • ARC as the higher-level controller
  • Arduino handling stepper/motion timing
  • clear command separation
  • one protocol for both directions
  • room to monitor navigation and course correction

If you want, I can do the next step and turn this into a clean numbered protocol document with:

  1. Command table
  2. Payload size table
  3. State transition table
  4. RUN = 0 / RUN = 1 behavior table

That would make it much easier to start coding from.


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

Hi @Athena, I am trying to verify ARC to Arduino UART functionality.  I need help getting the proper values to the ARC script for the UART to send.  I would like the ARC script to pick up global variables for the Waypoint Data.  Can you provide ARC code that will allow the previous posted ARC script to send the 'Waypoint Data' to the previous posted Arduino sketch?

// WAYPOINT DATA NEEDED TO SEND VIA ARC

  pathTotalDistance,
  combinedStepsDesired,
  oppositeDistance,
  bearingAlignment,  (DA or 0A)
  reflectorBearing,
  sensorPrime,
  desiredBearing,
  actualBearing,
  trackDir  ( FD or RD)

Thank you. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#16  

Absolutely,Jack - yes,ARC can pick up global variables and use them to build the Waypoint Data packet for your Arduino sketch.

The cleanest way is:

  1. Put your waypoint values into ARC global variables
  2. Let one JavaScript script read those globals
  3. Pack the values into a byte array
  4. Send the packet over UART.hardwareUartWrite()

1)Recommended ARC global variables

Use these globals in ARC:

  • $pathTotalDistance
  • $combinedStepsDesired
  • $oppositeDistance
  • $bearingAlignment
  • $reflectorBearing
  • $sensorPrime
  • $desiredBearing
  • $actualBearing
  • $trackDir
  • $sendWaypointNow trigger flag

Suggested value formats

Variable Example Type
$pathTotalDistance 120000 integer
$combinedStepsDesired 84000 integer
$oppositeDistance 24000 integer
$bearingAlignment 0 or 1 number
$reflectorBearing 97.00 number
$sensorPrime 4 integer
$desiredBearing 79.35 number
$actualBearing 79.00 number
$trackDir 0 or 1 number

2)Encoding note

To keep the packet reliable, I recommend encoding like this:

  • uint32 for the three large distances
  • uint8 for alignment, sensor prime, track direction
  • uint16 for bearings, multiplied by 100

So:

  • 79.35 becomes 7935
  • 97.00 becomes 9700

3)ARC JavaScript: Waypoint Data sender

This script reads the globals and sends the waypoint packet when $sendWaypointNow is 1.

var UART_INDEX = 0;
var UART_HEADER = 0xA5;
var CMD_WAYPOINT_DATA = 0x06;

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);
    print("Sent packet cmd=" + cmd + " length=" + packet.length);
}

function sendWaypointDataFromGlobals() {
    var pathTotalDistance = parseInt(getVar("$pathTotalDistance", "0"));
    var combinedStepsDesired = parseInt(getVar("$combinedStepsDesired", "0"));
    var oppositeDistance = parseInt(getVar("$oppositeDistance", "0"));

    var bearingAlignmentText = String(getVar("$bearingAlignment", "0"));
    var bearingAlignment = 0;

    if (bearingAlignmentText == "DA" || bearingAlignmentText == "0") {
        bearingAlignment = 0;
    } else if (bearingAlignmentText == "OA" || bearingAlignmentText == "1") {
        bearingAlignment = 1;
    } else {
        bearingAlignment = 0;
    }

    var reflectorBearing = parseFloat(getVar("$reflectorBearing", "0"));
    var sensorPrime = parseInt(getVar("$sensorPrime", "0"));
    var desiredBearing = parseFloat(getVar("$desiredBearing", "0"));
    var actualBearing = parseFloat(getVar("$actualBearing", "0"));

    var trackDirText = String(getVar("$trackDir", "FD"));
    var trackDir = 0;

    if (trackDirText == "FD" || trackDirText == "0") {
        trackDir = 0;
    } else if (trackDirText == "RD" || trackDirText == "1") {
        trackDir = 1;
    } else {
        trackDir = 0;
    }

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

    sendPacket(CMD_WAYPOINT_DATA, payload);

    print("Waypoint sent:");
    print("pathTotalDistance=" + pathTotalDistance);
    print("combinedStepsDesired=" + combinedStepsDesired);
    print("oppositeDistance=" + oppositeDistance);
    print("bearingAlignment=" + bearingAlignment);
    print("reflectorBearing=" + reflectorBearing);
    print("sensorPrime=" + sensorPrime);
    print("desiredBearing=" + desiredBearing);
    print("actualBearing=" + actualBearing);
    print("trackDir=" + trackDir);
}

while (true) {
    if (getVar("$sendWaypointNow", "0") == 1) {
        sendWaypointDataFromGlobals();
        setVar("$sendWaypointNow", 0);
    }

    sleep(50);
}

4)How to use it in ARC

Set the global variables first

You can set them from:

  • another script
  • a button
  • a control callback
  • your navigation logic

Then set the trigger

When you are ready to send the waypoint packet:

setVar("$sendWaypointNow", 1);

That tells the script to send the data once.


5)Example of setting the globals

Here is a simple example script that loads test values:

setVar("$pathTotalDistance", 120000);
setVar("$combinedStepsDesired", 84000);
setVar("$oppositeDistance", 24000);
setVar("$bearingAlignment", "DA");
setVar("$reflectorBearing", 97.00);
setVar("$sensorPrime", 4);
setVar("$desiredBearing", 79.35);
setVar("$actualBearing", 79.00);
setVar("$trackDir", "FD");
setVar("$sendWaypointNow", 1);

6)Important beginner note

Your Arduino sketch must decode the packet in the same byte order you send it.

That means the Arduino should expect:

  • header byte
  • command byte
  • pathTotalDistance as 4 bytes
  • combinedStepsDesired as 4 bytes
  • oppositeDistance as 4 bytes
  • bearingAlignment as 1 byte
  • reflectorBearing as 2 bytes
  • sensorPrime as 1 byte
  • desiredBearing as 2 bytes
  • actualBearing as 2 bytes
  • trackDir as 1 byte

7)One thing I want to confirm

Do you want me to now write the matching Arduino waypoint RX handler so it decodes this exact ARC packet correctly?

If yes, I can give you the Arduino code next in a clean beginner-friendly format.


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.