Script icon Script Multi-language ARC Script: build, run and debug Blockly, JavaScript, EZ-Script or Python with Intellisense, run/save/load and Roboscratch support 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

With ARC Pro, your robot is not just a machine; it's your creative partner in the journey of technological exploration.

#33  

Hi @Athena. Please send a clean matching global-variable initialization script for ARC.
Thanks. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#34  

Absolutely,Jack - here’s a clean matching ARC global-variable initialization script for your UART project.

It keeps all protocol values and trigger flags separate from the UART script, which is the simpler and safer approach.

ARC Global Variable Initialization Script

// UART project globals for ARC
// Run this once at startup or whenever you want to reset the protocol state

// -----------------------------
// Navigation / waypoint values
// -----------------------------
setVar("$pathTotalDistance", 120000);
setVar("$combinedStepsDesired", 84000);
setVar("$oppositeDistance", 24000);

setVar("$bearingAlignment", 0);       // 0 = DA, 1 = OA
setVar("$reflectorBearing", 97.00);   // degrees
setVar("$sensorPrime", 4);            // 1 to 10

setVar("$desiredBearing", 79.35);     // degrees, scaled logically by your protocol
setVar("$actualBearing", 79.00);       // degrees, scaled logically by your protocol

setVar("$trackDir", 0);               // 0 = FD, 1 = RD

// -----------------------------
// Pivot values
// -----------------------------
setVar("$rightPivotSteps", 2500);
setVar("$leftPivotSteps", 2500);

// -----------------------------
// UART trigger flags
// Set these to 1 from other scripts when you want a packet sent
// The UART script should clear them back to 0 after sending
// -----------------------------
setVar("$sendWaypointNow", 0);
setVar("$sendRightPivotNow", 0);
setVar("$sendLeftPivotNow", 0);
setVar("$sendActualBearingNow", 0);
setVar("$sendStopNow", 0);
setVar("$sendMissedIRNow", 0);
setVar("$sendObsScanCompleteNow", 0);

// -----------------------------
// Runtime/status flags
// -----------------------------
setVar("$run", 0);        // 0 = waypoint/setup phase, 1 = forward navigation
setVar("$obsScan", 0);    // set to 1 when Arduino requests obstacle scan

print("UART global variables initialized");

How this fits your setup

  • This script only sets values
  • Your ARC UART script only handles:
    • sending packets
    • reading packets
    • clearing trigger flags after use
  • Your Arduino sketch v2 handles:
    • parsing the packets
    • switching RUN
    • sending NAV ACK
    • sending status updates back to ARC
  1. Put this in a separate Script skill
  2. Run it once when the project starts
  3. Then use your UART trigger flags from other ARC scripts like:
setVar("$sendWaypointNow", 1);

or

setVar("$sendStopNow", 1);

Important note

If you want the project to stay easy to manage, I’d recommend:

  • keeping all packet values in this init script
  • keeping all UART send/receive logic in the UART script
  • using only trigger flags to request a send

That gives you a much cleaner architecture and makes debugging far easier.

If you want, I can next provide a final cleaned pair of:

  1. ARC UART script v2
  2. Arduino UART sketch v2

formatted together so you can copy them side-by-side and test them as a complete set.


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

Hi @Athena.  Please send a final cleaned pair of:

ARC UART script v2 Arduino UART sketch v2

Thanks.  Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#36  

Absolutely,Jack - below is a final cleaned pair that matches your current design:

  • ARC UART script v2 = trigger-based sending + packet reading
  • Arduino UART sketch v2 = RUN = 0 waypoint/setup phase, then RUN = 1 forward navigation phase

I’ve kept the code as simple and direct as possible.


1) ARC UART Script v2

Put this in your ARC Script skill that handles UART.

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;

function safeInt(v, def) {
    var n = parseInt(v);
    if (isNaN(n)) return def;
    return n;
}

function safeFloat(v, def) {
    var n = parseFloat(v);
    if (isNaN(n)) return def;
    return n;
}

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

function u32ToBytes(value) {
    value = value >>> 0;
    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, []);
    print("TX: STOP");
}

function sendMissedIR() {
    sendPacket(CMD_MISSED_IR, []);
    print("TX: MISSED IR");
}

function sendActualBearing() {
    var bearing = safeFloat(getVar("$actualBearing", -1), -1);
    if (bearing < 0) return;

    var bearing100 = Math.round(bearing * 100);
    sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
    print("TX: ACTUAL BEARING = " + bearing);
}

function sendRightPivot() {
    var steps = safeInt(getVar("$rightPivotSteps", 0), 0);
    sendPacket(CMD_RIGHT_PIVOT, u16ToBytes(steps));
    print("TX: RIGHT PIVOT = " + steps);
}

function sendLeftPivot() {
    var steps = safeInt(getVar("$leftPivotSteps", 0), 0);
    sendPacket(CMD_LEFT_PIVOT, u16ToBytes(steps));
    print("TX: LEFT PIVOT = " + steps);
}

function sendWaypointData() {
    var pathTotalDistance = safeInt(getVar("$pathTotalDistance", 0), 0);
    var combinedStepsDesired = safeInt(getVar("$combinedStepsDesired", 0), 0);
    var oppositeDistance = safeInt(getVar("$oppositeDistance", 0), 0);
    var bearingAlignment = safeInt(getVar("$bearingAlignment", 0), 0) & 0xFF;
    var reflectorBearing = safeFloat(getVar("$reflectorBearing", 0), 0);
    var sensorPrime = safeInt(getVar("$sensorPrime", 0), 0) & 0xFF;
    var desiredBearing = safeFloat(getVar("$desiredBearing", 0), 0);
    var actualBearing = safeFloat(getVar("$actualBearing", 0), 0);
    var trackDir = safeInt(getVar("$trackDir", 0), 0) & 0xFF;

    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);
    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("TX: WAYPOINT DATA");
}

function readIncomingPacket() {
    while (UART.hardwareUartAvailable(UART_INDEX) >= 2) {
        var header = UART.hardwareUartRead(UART_INDEX, 1)[0];
        if (header != UART_HEADER) {
            continue;
        }

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

        if (cmd == RSP_NAV_ACK) {
            print("RX: NAV ACK");
            setVar("$run", 1);
            continue;
        }

        if (cmd == RSP_TOTAL_STEPS) {
            while (UART.hardwareUartAvailable(UART_INDEX) < 4) {
                sleep(1);
            }

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

        if (cmd == RSP_DESIRED_BEARING) {
            while (UART.hardwareUartAvailable(UART_INDEX) < 2) {
                sleep(1);
            }

            var d = UART.hardwareUartRead(UART_INDEX, 2);
            var bearing100 = d[0] | (d[1] << 8);
            print("RX: DESIRED BEARING = " + (bearing100 / 100.0));
            continue;
        }

        if (cmd == RSP_OBS_SCAN_REQUEST) {
            print("RX: OBSTACLE SCAN REQUEST");
            setVar("$obsScan", 1);
            continue;
        }

        print("RX: Unknown command = " + cmd);
    }
}

while (true) {

    // TX triggers
    if (getVar("$sendStopNow", 0) == 1) {
        sendStopMotors();
        setVar("$sendStopNow", 0);
    }

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

    if (getVar("$sendActualBearingNow", 0) == 1) {
        sendActualBearing();
        setVar("$sendActualBearingNow", 0);
    }

    if (getVar("$sendRightPivotNow", 0) == 1) {
        sendRightPivot();
        setVar("$sendRightPivotNow", 0);
    }

    if (getVar("$sendLeftPivotNow", 0) == 1) {
        sendLeftPivot();
        setVar("$sendLeftPivotNow", 0);
    }

    if (getVar("$sendWaypointNow", 0) == 1) {
        sendWaypointData();
        setVar("$sendWaypointNow", 0);
    }

    if (getVar("$sendObsScanCompleteNow", 0) == 1) {
        sendPacket(CMD_OBS_SCAN_COMPLETE, []);
        setVar("$sendObsScanCompleteNow", 0);
        print("TX: OBS SCAN COMPLETE");
    }

    // RX processing
    readIncomingPacket();

    sleep(20);
}

2) Arduino UART Sketch v2

This is the matching Arduino Mega sketch using Serial3.

#include <Arduino.h>

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;

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;

int RUN = 0; // 0 = setup / pivots / waypoint receive, 1 = forward navigation

uint32_t totalStepsTaken = 0;
uint16_t desiredBearing100 = 0;
uint16_t actualBearing100 = 0;

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;

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 writeUInt16LE(uint16_t value) {
  Serial3.write((uint8_t)(value & 0xFF));
  Serial3.write((uint8_t)((value >> 8) & 0xFF));
}

void writeUInt32LE(uint32_t value) {
  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 sendHeaderAndCmd(uint8_t cmd) {
  Serial3.write(UART_HEADER);
  Serial3.write(cmd);
}

void sendNavAck() {
  sendHeaderAndCmd(RSP_NAV_ACK);
  Serial.println("TX: NAV ACK");
}

void sendTotalSteps(uint32_t steps) {
  sendHeaderAndCmd(RSP_TOTAL_STEPS);
  writeUInt32LE(steps);
  Serial.print("TX: TOTAL STEPS = ");
  Serial.println(steps);
}

void sendDesiredBearing(uint16_t bearing100) {
  sendHeaderAndCmd(RSP_DESIRED_BEARING);
  writeUInt16LE(bearing100);
  Serial.print("TX: DESIRED BEARING x100 = ");
  Serial.println(bearing100);
}

void sendObstacleScanRequest() {
  sendHeaderAndCmd(RSP_OBS_SCAN_REQUEST);
  Serial.println("TX: OBSTACLE SCAN REQUEST");
}

void stopMotors() {
  Serial.println("Action: stopMotors()");
  // add your motor stop logic here
}

void pivotRight(uint16_t steps) {
  Serial.print("Action: pivotRight(");
  Serial.print(steps);
  Serial.println(")");
  // add your right pivot logic here
}

void pivotLeft(uint16_t steps) {
  Serial.print("Action: pivotLeft(");
  Serial.print(steps);
  Serial.println(")");
  // add your left pivot logic here
}

void beginForwardNavigation() {
  Serial.println("Action: beginForwardNavigation()");
  RUN = 1;
}

void handleWaypointPacket(const uint8_t* data, uint8_t len) {
  if (len != 21) {
    Serial.print("Waypoint length mismatch: ");
    Serial.println(len);
    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];

  desiredBearing100 = waypointDesiredBearing100;
  actualBearing100 = waypointActualBearing100;

  Serial.println("RX: 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("reflectorBearing100 = "); Serial.println(reflectorBearing100);
  Serial.print("sensorPrime = "); Serial.println(sensorPrime);
  Serial.print("desiredBearing100 = "); Serial.println(desiredBearing100);
  Serial.print("actualBearing100 = "); Serial.println(actualBearing100);
  Serial.print("trackDir = "); Serial.println(trackDir);

  sendNavAck();
  beginForwardNavigation();
}

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

    case CMD_MISSED_IR:
      Serial.println("RX: MISSED IR");
      // add your missed-IR recovery logic here
      break;

    case CMD_ACTUAL_BEARING:
      if (len == 2) {
        actualBearing100 = readUInt16LE(data);
        Serial.print("RX: ACTUAL BEARING x100 = ");
        Serial.println(actualBearing100);
      }
      break;

    case CMD_RIGHT_PIVOT:
      if (len == 2 && RUN == 0) {
        uint16_t steps = readUInt16LE(data);
        Serial.print("RX: RIGHT PIVOT steps = ");
        Serial.println(steps);
        pivotRight(steps);
      }
      break;

    case CMD_LEFT_PIVOT:
      if (len == 2 && RUN == 0) {
        uint16_t steps = readUInt16LE(data);
        Serial.print("RX: LEFT PIVOT steps = ");
        Serial.println(steps);
        pivotLeft(steps);
      }
      break;

    case CMD_WAYPOINT_DATA:
      if (RUN == 0) {
        handleWaypointPacket(data, len);
      } else {
        Serial.println("RX: WAYPOINT ignored because RUN != 0");
      }
      break;

    case CMD_OBS_SCAN_COMPLETE:
      Serial.println("RX: OBSTACLE SCAN COMPLETE");
      // resume normal tracking behavior here
      break;

    default:
      Serial.print("RX: Unknown command 0x");
      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 21;
    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:
      if (payloadIndex < sizeof(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);
  }
}

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

void loop() {
  readIncomingUart();

  if (RUN == 0) {
    // setup / pivot / waypoint receive phase
  } else if (RUN == 1) {
    // forward navigation phase
    // put your step counting / PWM correction / bearing logic here
  }

  // optional small yield
  // delay(1);
}

Important notes

1) This pair uses fixed packet lengths

That is what makes it reliable.

2) WAYPOINT DATA length is 21 bytes

This matches your Arduino v2 sketch exactly.

3) desiredBearing and actualBearing

These are included in the waypoint packet, as requested.

4) RUN behavior

  • RUN = 0 receives:
    • WAYPOINT DATA
    • RIGHT PIVOT
    • LEFT PIVOT
  • RUN = 1 is for forward motion and status behavior

One question before you test

Do you want me to also provide a very small test script that only sends:

  • WAYPOINT DATA
  • NAV ACK confirmation
  • and then prints the received NAV ACK

That would be the safest first test before enabling pivots and bearing updates.


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.
#37   — Edited

Hi @Athena.  All three scripts (ARC Global Variable Initialization Script, ARC UART Script V-2, ARDUINO UART Sketch V-2) work beautifully as far as I have tested so far.  I have tested all functions of ARC Global Variable Initialization Script.  The triggers cause the appropriate UART TX & RX to take place in ARC and ARDUINO.

I have not tested the following and hope you can provide guidance on how to TX from ARDUINO UART the following objectives:

DESIRED BEARING UPDATE  //   OBSTACLE SCAN REQUEST Could you please explain how I can mod the ARDUINO UART to send this data?

Also, I would really like an explanation of the code (remember I am a beginner) for both the ARC UART and the ARDUINO UART.  It seems a perfect time to learn, now that it is sending and receiving data according to my stated objectives.  Can you describe the code for me one more time?

Thank you. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#38  

Great to hear it’s working, Jack - that’s a strong sign your packet structure and UART wiring are solid.

How the Arduino should send Desired Bearing Update and Obstacle Scan Request

In your current protocol:

  • Desired Bearing Update = Arduino ARC

    • Header: 0xA5
    • Command: 0x12
    • Payload: uint16 bearing × 100
  • Obstacle Scan Request = Arduino ARC

    • Header: 0xA5
    • Command: 0x13
    • Payload: none

Because you’re using binary packets, the Arduino just needs to write the header, then command byte, then payload bytes.


1) Add these helper functions to the Arduino sketch

Here’s the clean way to send both messages:

static const uint8_t UART_HEADER = 0xA5;
static const uint8_t RSP_DESIRED_BEARING = 0x12;
static const uint8_t RSP_OBS_SCAN_REQUEST = 0x13;

void writeUInt16LE(uint16_t value) {
  Serial3.write((uint8_t)(value & 0xFF));
  Serial3.write((uint8_t)((value >> 8) & 0xFF));
}

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

void sendDesiredBearingUpdate(uint16_t bearing100) {
  sendHeaderAndCmd(RSP_DESIRED_BEARING);
  writeUInt16LE(bearing100);
  Serial.print("TX:DESIRED BEARING x100 = ");
  Serial.println(bearing100);
}

void sendObstacleScanRequest() {
  sendHeaderAndCmd(RSP_OBS_SCAN_REQUEST);
  Serial.println("TX:OBSTACLE SCAN REQUEST");
}

2) When to call them

Send a desired bearing update

Call this whenever your Arduino computes a new desired bearing and you want ARC to display it:

sendDesiredBearingUpdate(desiredBearing100);

If you only want to send it when the value changes:

static uint16_t lastDesiredBearing100 = 0xFFFF;

if (desiredBearing100 != lastDesiredBearing100) {
  sendDesiredBearingUpdate(desiredBearing100);
  lastDesiredBearing100 = desiredBearing100;
}

Send an obstacle scan request

Call this when your Arduino reaches the scan trigger condition, for example a percentage of pathTotalDistance:

sendObstacleScanRequest();

A typical pattern would be:

if (combinedStepsTaken >= scanTriggerSteps) {
  sendObstacleScanRequest();
  // optionally pause correction / enter scan mode
}

3) ARC side behavior for those packets

Your ARC UART script already knows how to handle these:

  • 0x12 prints the desired bearing
  • 0x13 sets $obsScan = 1

So once the Arduino sends them, ARC will react automatically.


Beginner explanation of the ARC UART script

Here’s the purpose of each section in plain language.

A. Constants at the top

These are just labels for the packet bytes.

var UART_HEADER = 0xA5;
var CMD_STOP_MOTORS = 0x01;
...
var RSP_DESIRED_BEARING = 0x12;

Think of them as named numbers so the code is easier to read.


B. Helper functions

These convert numbers into bytes and send packets.

u16ToBytes(value)

Takes a number from 0 to 65535 and splits it into 2 bytes.

u32ToBytes(value)

Takes a bigger number and splits it into 4 bytes.

sendPacket(cmd, payload)

Builds a packet like this:

[HEADER][COMMAND][DATA...]

Then writes it to UART.


C. TX functions

These are the actions ARC can send to the Arduino.

  • sendStopMotors()
  • sendMissedIR()
  • sendActualBearing()
  • sendRightPivot()
  • sendLeftPivot()
  • sendWaypointData()

Each one packages the correct data and sends it out.


D. RX function

This reads packets coming back from Arduino.

When ARC sees:

  • 0x10 NAV ACK
  • 0x11 total steps
  • 0x12 desired bearing
  • 0x13 obstacle scan request

it prints or updates globals.


E. Main loop

This loop does two jobs:

  1. Check trigger flags like $sendWaypointNow
  2. Read incoming UART packets from Arduino

That’s why it works well:

  • ARC only sends when asked
  • ARC always listens for replies

Beginner explanation of the Arduino UART sketch

A. Constants

These are the same packet IDs ARC uses.

Both sides must agree on the same values.


B. Parser state machine

This is the most important concept.

The Arduino is waiting for bytes in this order:

  1. Header
  2. Command
  3. Payload

That’s why the code uses states like:

  • WAIT_HEADER
  • WAIT_CMD
  • WAIT_PAYLOAD

This prevents confusion if a byte is lost or noise appears on the line.


C. handleWaypointPacket()

This function receives the full waypoint packet and stores the values into variables:

  • path distance
  • combined steps desired
  • opposite distance
  • bearing alignment
  • reflector bearing
  • sensor prime
  • desired bearing
  • actual bearing
  • track direction

Then it sends:

  • NAV ACK

and switches to:

  • RUN = 1

D. handlePacket()

This is the command dispatcher.

It looks at the command byte and decides what to do:

  • stop motors
  • pivot right
  • pivot left
  • receive waypoint
  • receive actual bearing
  • receive obstacle scan complete

E. RUN = 0 and RUN = 1

This is your operating mode system.

RUN = 0

Used for:

  • setup
  • pivots
  • waypoint receive

RUN = 1

Used for:

  • forward motion
  • step counting
  • PWM correction
  • desired bearing updates
  • obstacle scan requests

That’s a very good structure.


What I recommend next

To make your Arduino even clearer, I can provide a commented Arduino v2 sketch where I add beginner-friendly comments line by line, and I can also do the same for the ARC script.

If you want, I can produce:

  1. Arduino sketch v2 with detailed comments
  2. ARC UART script v2 with detailed comments

That would make it much easier to learn while keeping your project stable.


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

Hi @Athena, After studying the existing ARDUINO UART sketch, I found the following:

void sendDesiredBearing(uint16_t bearing100) {
  sendHeaderAndCmd(RSP_DESIRED_BEARING);
  writeUInt16LE(bearing100);
  Serial.print("TX: DESIRED BEARING x100 = ");
  Serial.println(bearing100);
}

This precluded the need for any additional 'update' since you have already taken this into account in the sketch.

void sendDesiredBearingUpdate(uint16_t bearing100) {
  sendHeaderAndCmd(RSP_DESIRED_BEARING);
  writeUInt16LE(bearing100);
  Serial.print("TX:DESIRED BEARING x100 = ");
  Serial.println(bearing100);
}

I simply called for 'sendHeaderAndCmd(RSP_DESIRED_BEARING)' and it produced the expected results.

Thank you again for the explanation of the UART code.  Both work very well in communicating the desired UART Data.  The more I study this code the clearer it will become.

Now, I would like to develop the ARDUINO Sketch and the ARC Script to their ultimate completion by adding PWM and computation of both Desired Bearing and Actual Bearing.  I'm not sure this thread is the best place for continuing because my UART questions have been answered.  Can you suggest a thread 'Name' that would be a good choice going forward?

Thanks.  Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#40  

That’s a great next step,Jack - and yes,for the new topic it’s better to start a fresh thread so the discussion stays focused and easier to follow.

A good thread name would be something like:

Arduino Mega PWM + Desired/Actual Bearing Computation for ARC UART Rover

Other good options:

  • ARC Arduino Rover: PWM and Bearing Control Integration
  • Arduino Mega Stepper PWM and Bearing Logic in ARC Project
  • UART Rover Expansion: PWM Correction and Bearing Math
  • ARC Rover Navigation: Arduino PWM and Bearing Update Logic

If you want the most descriptive and beginner-friendly title,I’d recommend:

ARC Rover: Arduino PWM Control and Bearing Computation

That tells people immediately:

  • it’s about ARC
  • it involves the Arduino
  • the focus is PWM and bearing math
  • it’s part of your rover navigation project

When you start the new thread, include:

  • your current UART packet table
  • what your Desired Bearing calculation should do
  • how Actual Bearing is measured
  • how PWM should respond when the error changes

That will make it much easier to help you build the next phase cleanly.

If you’d like,I can also help you draft the first post for that new thread so you can paste it in directly.


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.