Inverted Pendulum icon Inverted Pendulum Balancing controller for Sainsmart v3 (and compatible) robots: PID tuning, real‑time telemetry, angle offset, movement and calibration via ARC. Try it →
Asked — Edited
Resolved Resolved by Athena!

Arduino PWM Bearing Control Help

Hi @Athena,

I'm requesting help to add capabilities to an existing Arduino sketch. The sketch is included at the end of this post. Can you incorporate stepper motor PWM control (for four stepper motors) and add the Desired Bearing computations into the existing Arduino loop? Thank you in advance.

For clarity, here is the system context:

  • ARC (Synthiam Autonomous Robot Control) runs on an EZB4 and provides the high-level navigation, vision, and script coordination.
  • The Arduino Mega handles real-time hardware tasks (stepper motor control and Desired Bearing calculations). Communication between ARC (on the EZB4) and the Arduino Mega is via UART (Serial3 in the sketch).
  • The IR distance sensor and its 0-180 servo are powered and monitored as part of the Arduino/EZB4 architecture. The IR sensor is mounted front-and-center and tracks a fixed reflector located near floor level.
  • The ARC-side logic is implemented as Robot Skills (ROVER_CMD_v1, LEG_v1, IR_GEN_v1, UART_v1, IR_TRACKER_v1). The UART_v1 robot skill is responsible for all UART communication with the Arduino.
  • The requested Arduino logic should implement the Pre-Run Loop (pivots, waypoint receive, stop handling) and the Run Loop (forward navigation: step counting, PWM ramping, Desired Bearing computation, Bearing Diff ramping, Obstacle Scan Requests, and packet responses) as described below.

The full project overview, requirements, packet definitions, and the Arduino sketch to be modified are included below for reference.

ARC Rover: Arduino PWM Control and Bearing Computation

PART 1.   General Overview
PART 2.   Term Definitions
PART 3.   Requested Criteria for the Arduino Sketch
PART 4.   Arduino Sketch to be modified with PART 3 Criteria

PART 1
General Overview for informational purposes:

This rover is 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 the IR sensor servo. The IR sensor is mounted to a 0-180 degree servo centered at 90 degrees. This gives the IR sensor equal travel left (180) and right (0) of center (90).  The IR sensor/servo is front and center of the rover and tracks a single fixed reflector located near floor level across the room.
The principal purpose of this rover is to explore and develop a navigation system based upon the 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 an Arduino Mega.  Necessary communication between ARC and the Arduino is via UART.

This rover project is very much a work in progress. Different strategies for motor control, reflector sensing, bearing calculation and obstacle detection have been tried and modified to arrive at the present iteration. What follows represents the latest embodiment of this rover project.

PWM control is a skid/steer format that runs (Left Front/Left Back) as a pair and (Right Front/Right Back) as a pair for skid/steer action. Other mecanum wheel capabilities will be developed as necessary.

ARC Rover Control Architecture:
Individual ARC Scripts for Overall Rover Control

1. ROVER_CMD_v1:
This script controls all ARC scripts through 'control Command'.

2. LEG_v1:
This script defines the Waypoint Data for the rover path. It sets global variables necessary for each leg and operates only once.

3. IR_GEN_v1:
This script generates current IR readings and sets '$obsDet=1' when the obstacle detection threshold is exceeded. It operates in a continuous loop.

4. UART_v1:
This script is responsible for all UART communication with the Arduino. It operates in a continuous loop, acting upon global variable status.

5. IR_TRACKER_v1:
This script interprets the reflected infrared light received by the IR sensor. Before rover forward motion, the IR sensor voltage is noted as Cardinal IR. When the Desired Bearing equals the Actual Bearing, the rover is on course.  As the rover moves, the IR sensor voltage will change from Cardinal IR as the rover drifts off bearing with the reflector.  The change from Cardinal IR represents a bearing deviation and a new Actual Bearing is calculated and sent to the Arduino.

PART 2
Term Definitions:

Actual Bearing:  The actual angle between the IR sensor and the reflector. ARC calculation.

Desired Bearing:  The desired angle, between the IR sensor and the reflector, necessary to remain on course. 
                  Arduino calculation.
 
Bearing Diff:  The difference between Actual Bearing and Desired Bearing.  Steering PWM is applied to Left Motors
               and Right Motors based upon (Bearing Diff = Actual Bearing - Desired Bearing).
               Actual Bearing is sent to the Arduino by ARC as an X100 representation.

      EXAMPLE (Actual Bearing of 79.00 is sent by ARC as 7900). Desired Bearing is calculated by the Arduino and placed into the X100
              representation. Bearing Diff need only be recalculated when ARC sends a new Actual Bearing or the Arduino calculates a new Desired Bearing.

Leg always refers to the physical track of the rover.  Measured in inches and converted to Combined Steps. One inch = 437.5 Combined Steps.

Leg Start Point:  The beginning point of navigation.

Leg Stop Point:  The end point of navigation.

Leg Combined Steps:  The total PWM steps accrued, from all 4 motors, for any given distance.  One inch = 437.5 Combined Steps.

Leg Combined Steps Total:  The length of the leg defined by Leg Combined Steps.  The distance between the Leg Start Point and the Leg Stop Point.
                           
Leg Combined Steps Taken:  The distance traveled in Leg Combined Steps.

Leg Combined Steps Remaining:  The distance remaining in Leg Combined Steps.  (Leg Combined Steps Total - Leg Combined Steps Taken).

RIGHT TRIANGLE TERMS:
'Path' always extends beyond the 'Leg' and the 'Leg' is always a subset of Path. One inch = 437.5 Combined Steps.

'Adjacent Side'
Path Distance Total: Path Distance Total is the 'Adjacent side' of a right triangle. It begins at the Leg Start Point and extends well
                      beyond the Leg Stop Point. Its length terminates at the perpendicular intersection of the Reflector Plane Distance. Measured
                      in Leg Combined Steps.
 
Path Distance Taken:  The distance traveled, measured in Leg Combined Steps.

Path Distance Remaining:  The distance remaining measured in Leg Combined Steps (Path Distance Total - Path Distance Taken).

'Opposite Side'
Reflector Plane Distance: The Reflector Plane Distance is the 'Opposite side' of a right triangle. The Reflector Plane Distance                                                                                              
                            is always perpendicular to the Path Distance Total. Its length is a fixed distance, measured in Leg Combined Steps,                                                                             

                            between the Path Distance Total and the physical reflector.

PART 3
Requested Criteria to be added to the Arduino Sketch:

    1. PWM Capabilities using 4 stepper motors operating using libraries 'AccelStepper.h' and 'MultiStepper.h'

           AccelStepper LeftFrontWheel(1, 3, 6); // pins
           AccelStepper LeftBackWheel(1, 2, 5);  // pins
           AccelStepper RightFrontWheel(1, 12, 13);  // pins
           AccelStepper RightBackWheel(1, 4, 7);  // pins

    Pre-Run Loop:  Pre-Run Loop is the default loop and is active only when Run Loop is deactivated.
    Monitors for the following packets/commands sent by ARC:

    Each pass through the Pre-Run Loop:

    1. Stop Motors:  Implement 'Stop Motors' when Arduino receives a notification ('Obstacle Detected' = 1).
                     Respond to Stop Motor command by sending 'Total Steps Taken' to ARC.

    2. Right Pivot:  Pivot in place in the direction and for the number of Combined Steps sent by ARC. Pivots reaching the specified Combined Steps should implement
                    'Stop Motors' and send 'Total Steps Taken' to ARC.

    3. Left Pivot:  Pivot in place in the direction and for the number of Combined Steps sent by ARC.  Pivots reaching the specified Combined Steps should implement
                   'Stop Motors' and send 'Total Steps Taken' to ARC.

    4. Waypoint Data:  When the 'Waypoint Data' packet is received by Arduino, send a 'Navigation Acknowledgement' to ARC.

    Run Loop:  Run Loop is activated when Navigation Acknowledgement is sent to ARC.  Pre-Run Loop is deactivated.
    Monitors for the following packets/conditions sent by ARC:

    Each pass through the Run Loop:

    1. Counts Combined Steps:
       Monitor Combined Steps Taken and stop motors at Combined Steps Total (leg completion).

    2. Motor starting PWM is applied with a 'ramp' up to normal speed.  Incremental ramp steps accrue during each pass through the Run Loop.  
       When normal speed is reached, this ramp up to normal speed is terminated.

    3. Monitors when Actual Bearing changes are sent by ARC.  Actual Bearing is received as (XX * 100) and will remain in this 4-place integer in Arduino.

    4. Calculates Desired Bearing at selected intervals. 
       Desired Bearing is converted from XX.XX to (XX.XX * 100) and remains in this 4-place integer.
       Desired Bearing is sent to ARC periodically (for example, when Desired Bearing changes by 1.0 degree), so ARC can monitor deviations between Actual Bearing and Desired Bearing. 
   
       (See SUBSECTION: DESIRED BEARING CALCULATIONS below for more on Desired Bearing)

 NOTE:  Whenever Actual Bearing OR Desired Bearing changes, a Bearing Diff must be calculated.

    5. Bearing Diff:

     a.  Bearing Diff is reset to '0'.  Bearing Diff proportionally controls PWM differential and at '0' sets a neutral differential.
     b.  A new Bearing Diff is calculated. (Actual Bearing - Desired Bearing = Bearing Diff)
     c.  Bearing Diff is 'ramped' from '0' to the new Bearing Diff.  Small increments of (10) are added to Bearing Diff and PWM is remapped with each Run Loop
         pass until the new Bearing Diff is reached.
         PWM correction always begins with neutral differential PWM and ramps up to the required differential PWM for the recalculated Bearing Diff.

 Example:  Actual Bearing = 8800 (88.00).  Desired Bearing = 9000 (90.00).  Bearing Diff = -200.    (8800 - 9000) = -200
           Bearing Diff is set to '0'.  Each pass through Run Loop accumulates an additional (-10).  When Bearing Diff accumulates to -200, it remains until
           Actual Bearing or Desired Bearing changes prompting a recalculation of Bearing Diff.

           Actual Bearing = 9200 (92.00).  Desired Bearing = 9000 (90.00).  Bearing Diff = 200.     (9200 - 9000) = 200
           Bearing Diff is set to '0'.  Each pass through Run Loop accumulates an additional (+10).  When Bearing Diff accumulates to +200, it remains until
           Actual Bearing or Desired Bearing changes prompting a recalculation of Bearing Diff.

     6. Obstacle Scan Request:  Obstacle scan initiated based upon Combined Steps traveled at approximately 24-inch intervals.
     When this request is sent by Arduino to ARC, Neutral PWM must be applied to the Left and Right motors. Differential PWM will
     resume when ARC completes the obstacle scan and sends an Actual Bearing to Arduino, indicating the Obstacle Scan Request has been fulfilled.

     7. Stop Motors:  Implement Stop Motors when ARC sends a notification ('Obstacle Detected' = 1).  
                      Send Total Steps Taken in response to the Stop Motor command.

        SUBSECTION:     DESIRED BEARING CALCULATIONS

    Desired Bearing is calculated entirely by the Arduino in ONE of two ways (A or B):

    A) If Waypoint Data received indicates Bearing Alignment 0 (DA), then the Desired Bearing is fixed and never changes, remaining at 90.  [90*100 = 9000]
    This Direct Alignment (DA) means the reflector is positioned directly in front of the rover and its path centerline will lead directly to the
    reflector. Desired Bearing is never recalculated.

    B) If Waypoint Data received indicates Bearing Alignment 1 (OA), then the Desired Bearing will constantly change as the rover moves along.  The
    Desired Bearing may, for instance, start at 83.00 [8300] and end at 71.00 [7100].  This Offset Alignment (OA) means the reflector
    is positioned offset from the rover and its path centerline. Desired Bearing is calculated at Combined Step intervals of 100.

    The trigonometry calculation of a right triangle is used for an Offset Alignment (OA) reflector:

                       Tangent = Opposite (Reflector Plane Distance) / Adjacent (Path Distance Remaining)

    If Waypoint Data received indicates Track Direction = 0 (FD), then the following is applied for forward direction:
    Path Distance Remaining = (pathDistanceTotal - abs(legCombinedStepsTaken));

    If Waypoint Data received indicates Track Direction = 1 (RD), then the following is applied for reverse direction:
    Path Distance Remaining = pathDistanceTotal - (legCombinedStepsTotal - abs(legCombinedStepsTaken));

    if (desiredBearing < 90) {
      desiredBearing = (atan2(reflectorPlaneDistance, PathDistanceRemaining) * 180/PI) - 90;
      // a positive angle is required, use the absolute value
      desiredBearing = abs(desiredBearing);
    }
    // Use atan2 to calculate the angle correctly
    if (desiredBearing > 90) {
      desiredBearing = (atan2(reflectorPlaneDistance, PathDistanceRemaining) * 180/PI) + 90;
    }

PACKETS:

ARC -> Arduino Packets:

1. Stop Motors (rover full stop)

Byte	Value
Header	0xA5
Command	0x01
Payload	none
Packet size: 2 bytes

2. Actual Bearing (Bearing to the reflector)

Actual bearing is sent as a whole number scaled by 100.
Example:
79.00 degrees becomes 7900
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

3. Right Pivot

Byte	Value
Header	0xA5
Command	0x04
Byte 2	Low byte of step count
Byte 3	High byte of step count
Packet size: 4 bytes

4. Left Pivot

Byte	Value
Header	0xA5
Command	0x05
Byte 2	Low byte of step count
Byte 3	High byte of step count
Packet size: 4 bytes

5. Waypoint Data

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

1. Navigation Acknowledgement (confirmation of Waypoint Data received)

Byte	Value
Header	0xA5
Command	0x10
Payload	none
Packet size: 2 bytes

2. Total Steps Taken (response to Stop Motor command)

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 (course computed for desired rover path)

Byte	Value
Header	0xA5
Command	0x12
Byte 2	Low byte
Byte 3	High byte
Packet size: 4 bytes

4. Obstacle Scan Request (obstacle scan initiated based upon distance traveled)

Byte	Value
Header	0xA5
Command	0x13
Payload	none
Packet size: 2 bytes
PART 4.  
Arduino Sketch to be modified:
#include <Arduino.h>
static const uint8_t UART_HEADER = 0xA5;
// ARC -> Arduinostatic 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 -> ARCstatic 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 lastDesiredBearing100 = 1;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
    // TEST ARDU TX FUNCTIONS
    

delay(100);

  }
  

If you need any additional clarification about how ARC Robot Skills are being used, the UART packet timing, or which variables ARC expects to be updated, let me know and I will provide that detail.

Thanks again, Jack



Related Hardware (view all EZB hardware)
Synthiam ARC enables Arduino Mega 2560 control with 8 ADC, 13 PWM, 21 digital ports and 24 servos; includes firmware, shield and tutorials
USB
Servos 24
Camera
Audio
UART 3
I2C
ADC 8
Digital 21

Related Robot Skills (view all robot skills)
Manage and execute multiple scripts in a single Script Manager using ControlCommand(), with Control Details showing available commands.
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

Get access to the latest features and updates before they're released. You'll have everything that's needed to unleash your robot's potential!

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#25  

Below is a clean, paste-ready Arduino Mega sketch that matches your current ARC flow as closely as possible and fixes the major syntax/parser issues in the sketch you posted.

It implements:

  • UART packet parsing for:
    • STOP_MOTORS
    • ACTUAL_BEARING
    • RIGHT_PIVOT
    • LEFT_PIVOT
    • WAYPOINT_DATA
  • Arduino ARC responses for:
    • NAV_ACK
    • TOTAL_STEPS
    • DESIRED_BEARING
    • OBS_SCAN_REQUEST
  • Pre-run mode for waypoint receive and blocking pivots
  • Run mode for forward navigation
  • Desired Bearing computation
  • Bearing diff ramping
  • Periodic obstacle scan request
  • Step-based navigation using four DRV8825 stepper drivers

Arduino Mega Sketch

#include <AccelStepper.h>
#include <MultiStepper.h>

// ----------------------------------------------------
// UART PACKETS
// ----------------------------------------------------
static const uint8_t UART_HEADER = 0xA5; // ARC -> Arduino

static const uint8_t CMD_STOP_MOTORS       = 0x01;
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;

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

// ----------------------------------------------------
// STEPPERS
// ----------------------------------------------------
// DRV8825 step/dir wiring
AccelStepper LeftFrontWheel(AccelStepper::DRIVER, 3, 6);
AccelStepper LeftBackWheel(AccelStepper::DRIVER, 2, 5);
AccelStepper RightFrontWheel(AccelStepper::DRIVER, 12, 13);
AccelStepper RightBackWheel(AccelStepper::DRIVER, 4, 7);

MultiStepper multiStepper;

// ----------------------------------------------------
// PARSER STATE
// ----------------------------------------------------
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;

// ----------------------------------------------------
// NAV STATE
// ----------------------------------------------------
int RUN = 0; // 0 = pre-run, 1 = forward run

uint32_t totalStepsTaken = 0;
uint32_t combinedStepsDesired = 0;
uint32_t pathTotalDistance = 0;
uint32_t oppositeDistance = 0;

uint8_t bearingAlignment = 0; // 0 = DA, 1 = OA
uint16_t reflectorBearing100 = 0;
uint8_t sensorPrime = 0;
uint16_t waypointDesiredBearing100 = 9000;
uint16_t waypointActualBearing100 = 9000;
uint8_t trackDir = 0; // 0 = FD, 1 = RD

uint16_t actualBearing100 = 9000;
uint16_t desiredBearing100 = 9000;
uint16_t lastSentDesiredBearing100 = 0xFFFF;

int16_t bearingDiffTarget = 0;
int16_t bearingDiffRamped = 0;

uint32_t lastObstacleScanStep = 0;
bool obstacleScanOutstanding = false;

uint32_t currentStepTarget = 0;
bool legCompleteSent = false;

// motion tuning
uint16_t startSpeed = 0;
uint16_t targetSpeed = 600;   // tune for your drivetrain
uint16_t speedRampStep = 10;  // speed increase per loop
uint16_t maxSpeed = 1200;     // safety cap

// bearing tuning
int16_t bearingRampStep = 10;

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

// ----------------------------------------------------
// MOTOR CONTROL
// ----------------------------------------------------
void setAllMotorSpeeds(long leftSpeed, long rightSpeed) {
  LeftFrontWheel.setSpeed(leftSpeed);
  LeftBackWheel.setSpeed(leftSpeed);
  RightFrontWheel.setSpeed(rightSpeed);
  RightBackWheel.setSpeed(rightSpeed);
}

void stopMotors() {
  LeftFrontWheel.stop();
  LeftBackWheel.stop();
  RightFrontWheel.stop();
  RightBackWheel.stop();

  LeftFrontWheel.setSpeed(0);
  LeftBackWheel.setSpeed(0);
  RightFrontWheel.setSpeed(0);
  RightBackWheel.setSpeed(0);

  Serial.println("Action: stopMotors()");
}

void resetRuntimeMotionState() {
  obstacleScanOutstanding = false;
  lastObstacleScanStep = totalStepsTaken;
  bearingDiffRamped = 0;
  bearingDiffTarget = 0;
  currentStepTarget = combinedStepsDesired;
  legCompleteSent = false;
}

void pivotRight(uint16_t steps) {
  Serial.print("Action: pivotRight(");
  Serial.print(steps);
  Serial.println(")");

  long positions[4];
  positions[0] = LeftFrontWheel.currentPosition() + steps;
  positions[1] = LeftBackWheel.currentPosition() + steps;
  positions[2] = RightFrontWheel.currentPosition() - steps;
  positions[3] = RightBackWheel.currentPosition() - steps;

  multiStepper.moveTo(positions);
  while (multiStepper.run()) {
    // blocking pivot
  }

  totalStepsTaken += steps;
  stopMotors();
  sendTotalSteps(totalStepsTaken);
}

void pivotLeft(uint16_t steps) {
  Serial.print("Action: pivotLeft(");
  Serial.print(steps);
  Serial.println(")");

  long positions[4];
  positions[0] = LeftFrontWheel.currentPosition() - steps;
  positions[1] = LeftBackWheel.currentPosition() - steps;
  positions[2] = RightFrontWheel.currentPosition() + steps;
  positions[3] = RightBackWheel.currentPosition() + steps;

  multiStepper.moveTo(positions);
  while (multiStepper.run()) {
    // blocking pivot
  }

  totalStepsTaken += steps;
  stopMotors();
  sendTotalSteps(totalStepsTaken);
}

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

  // initialize runtime state
  resetRuntimeMotionState();

  // if DA, lock desired bearing at 90.00
  if (bearingAlignment == 0) {
    desiredBearing100 = 9000;
  } else {
    desiredBearing100 = waypointDesiredBearing100;
  }

  sendDesiredBearing(desiredBearing100);
}

// ----------------------------------------------------
// DESIRED BEARING
// ----------------------------------------------------
void computeDesiredBearing() {
  if (bearingAlignment == 0) {
    desiredBearing100 = 9000;
    return;
  }

  if (combinedStepsDesired == 0) return;

  int32_t pathDistanceRemaining = 0;

  if (trackDir == 0) {
    // forward direction
    if (pathTotalDistance > totalStepsTaken) {
      pathDistanceRemaining = (int32_t)(pathTotalDistance - totalStepsTaken);
    } else {
      pathDistanceRemaining = 1;
    }
  } else {
    // reverse direction
    if (pathTotalDistance > (combinedStepsDesired - totalStepsTaken)) {
      pathDistanceRemaining = (int32_t)(pathTotalDistance - (combinedStepsDesired - totalStepsTaken));
    } else {
      pathDistanceRemaining = 1;
    }
  }

  if (pathDistanceRemaining < 1) pathDistanceRemaining = 1;

  float tangent = (float)oppositeDistance / (float)pathDistanceRemaining;
  float deg = atan(tangent) * 180.0 / PI;

  uint16_t computed = (uint16_t)((deg + 90.0) * 100.0);

  desiredBearing100 = computed;

  if (lastSentDesiredBearing100 == 0xFFFF || abs((int32_t)desiredBearing100 - (int32_t)lastSentDesiredBearing100) >= 100) {
    sendDesiredBearing(desiredBearing100);
    lastSentDesiredBearing100 = desiredBearing100;
  }
}

// ----------------------------------------------------
// BEARING DIFF RAMP
// ----------------------------------------------------
void updateBearingDiff() {
  bearingDiffTarget = (int16_t)actualBearing100 - (int16_t)desiredBearing100;

  if (bearingDiffRamped < bearingDiffTarget) {
    bearingDiffRamped += bearingRampStep;
    if (bearingDiffRamped > bearingDiffTarget) bearingDiffRamped = bearingDiffTarget;
  } else if (bearingDiffRamped > bearingDiffTarget) {
    bearingDiffRamped -= bearingRampStep;
    if (bearingDiffRamped < bearingDiffTarget) bearingDiffRamped = bearingDiffTarget;
  }
}

// ----------------------------------------------------
// RUN LOOP
// ----------------------------------------------------
void runNavigationLoop() {
  // ramp toward target speed
  if (currentStepTarget == 0) currentStepTarget = combinedStepsDesired;

  if (totalStepsTaken >= currentStepTarget) {
    if (!legCompleteSent) {
      stopMotors();
      sendTotalSteps(totalStepsTaken);
      legCompleteSent = true;
    }
    RUN = 0;
    return;
  }

  if (currentStepTarget > 0 && (totalStepsTaken - lastObstacleScanStep) >= (uint32_t)(24.0 * 437.5) && !obstacleScanOutstanding) {
    sendObstacleScanRequest();
    obstacleScanOutstanding = true;

    // neutral PWM/neutral speed while continuing to move
    // this keeps rover moving but removes correction
    bearingDiffRamped = 0;
  }

  // compute desired bearing
  computeDesiredBearing();

  // if actual/desired changed, update diff target
  updateBearingDiff();

  // speed ramp
  if (startSpeed < targetSpeed) {
    startSpeed += speedRampStep;
    if (startSpeed > targetSpeed) startSpeed = targetSpeed;
  }

  if (startSpeed > maxSpeed) startSpeed = maxSpeed;

  // apply neutral correction during scan request
  long leftSpeed = startSpeed;
  long rightSpeed = startSpeed;

  if (!obstacleScanOutstanding) {
    int correction = bearingDiffRamped / 10;
    leftSpeed -= correction;
    rightSpeed += correction;
  }

  // safety clamp
  if (leftSpeed < 0) leftSpeed = 0;
  if (rightSpeed < 0) rightSpeed = 0;
  if (leftSpeed > maxSpeed) leftSpeed = maxSpeed;
  if (rightSpeed > maxSpeed) rightSpeed = maxSpeed;

  setAllMotorSpeeds(leftSpeed, rightSpeed);

  // run the steppers
  LeftFrontWheel.runSpeed();
  LeftBackWheel.runSpeed();
  RightFrontWheel.runSpeed();
  RightBackWheel.runSpeed();

  // step counting using accumulated absolute position
  totalStepsTaken =
    (uint32_t)abs(LeftFrontWheel.currentPosition()) +
    (uint32_t)abs(LeftBackWheel.currentPosition()) +
    (uint32_t)abs(RightFrontWheel.currentPosition()) +
    (uint32_t)abs(RightBackWheel.currentPosition());

  // if ARC already handled the obstacle and sent new actual bearing,
  // clear scan hold when new bearing arrives
}

// ----------------------------------------------------
// WAYPOINT HANDLING
// ----------------------------------------------------
void handleWaypointPacket(const uint8_t* data, uint8_t len) {
  // Expected packet layout from ARC script:
  // 0-3  pathTotalDistance
  // 4-7  combinedStepsDesired
  // 8-11 oppositeDistance
  // 12   bearingAlignment
  // 13-14 desiredBearing100
  // 15-16 reserved/padding
  // 17   trackDir
  if (len != 18) {
    Serial.print("Waypoint length mismatch: ");
    Serial.println(len);
    return;
  }

  pathTotalDistance = readUInt32LE(&data[0]);
  combinedStepsDesired = readUInt32LE(&data[4]);
  oppositeDistance = readUInt32LE(&data[8]);
  bearingAlignment = data[12];
  waypointDesiredBearing100 = readUInt16LE(&data[13]);
  sensorPrime = 0; // reserved in this aligned version
  trackDir = data[17];

  // default actual bearing from ARC-side value already staged
  waypointActualBearing100 = (uint16_t)readUInt16LE(&data[13]); // harmless placeholder if needed

  desiredBearing100 = waypointDesiredBearing100;
  actualBearing100 = waypointDesiredBearing100;

  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("desiredBearing100 = "); Serial.println(desiredBearing100);
  Serial.print("trackDir = "); Serial.println(trackDir);

  sendNavAck();
  beginForwardNavigation();
}

// ----------------------------------------------------
// PACKET HANDLING
// ----------------------------------------------------
uint8_t getExpectedLength(uint8_t cmd) {
  switch (cmd) {
    case CMD_STOP_MOTORS:    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 18;
    default:                 return 0xFF;
  }
}

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_ACTUAL_BEARING:
      if (len == 2) {
        actualBearing100 = readUInt16LE(data);
        Serial.print("RX: ACTUAL BEARING x100 = ");
        Serial.println(actualBearing100);

        // update scan state if we're waiting for ARC to resume
        if (obstacleScanOutstanding) {
          obstacleScanOutstanding = false;
          lastObstacleScanStep = totalStepsTaken;
        }

        // recalc bearing diff immediately
        bearingDiffTarget = (int16_t)actualBearing100 - (int16_t)desiredBearing100;
      }
      break;

    case CMD_RIGHT_PIVOT:
      if (len == 2 && RUN == 0) {
        pivotRight(readUInt16LE(data));
      }
      break;

    case CMD_LEFT_PIVOT:
      if (len == 2 && RUN == 0) {
        pivotLeft(readUInt16LE(data));
      }
      break;

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

    default:
      Serial.print("RX: Unknown command 0x");
      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 = 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);

  LeftFrontWheel.setMaxSpeed(2000);
  LeftBackWheel.setMaxSpeed(2000);
  RightFrontWheel.setMaxSpeed(2000);
  RightBackWheel.setMaxSpeed(2000);

  LeftFrontWheel.setAcceleration(800);
  LeftBackWheel.setAcceleration(800);
  RightFrontWheel.setAcceleration(800);
  RightBackWheel.setAcceleration(800);

  multiStepper.addStepper(LeftFrontWheel);
  multiStepper.addStepper(LeftBackWheel);
  multiStepper.addStepper(RightFrontWheel);
  multiStepper.addStepper(RightBackWheel);

  Serial.println("Arduino UART sketch ready");
}

void loop() {
  readIncomingUart();

  if (RUN == 0) {
    // pre-run mode
  } else if (RUN == 1) {
    runNavigationLoop();
  }
}

Important corrections in this sketch

1. Waypoint packet size

I aligned the ARC script and Arduino sketch to use an 18-byte payload after:

  • 0xA5
  • 0x06

So the UART packet is:

  • Header
  • Command
  • 18-byte payload

2. sendWaypointData() in ARC

This sketch expects:

  • pathTotalDistance
  • combinedStepsDesired
  • oppositeDistance
  • bearingAlignment
  • desiredBearing100
  • 2 reserved bytes
  • trackDir

3. ACTUAL_BEARING

Whenever ARC sends a new actual bearing packet, Arduino updates:

  • actualBearing100
  • bearingDiffTarget

4. Neutral scan behavior

When the Arduino sends OBS_SCAN_REQUEST, it:

  • keeps moving
  • sets correction to neutral
  • waits for ARC to finish scan work
  • resumes correction when a new actual bearing packet arrives

One important caveat

Your current ROVER_CMD_v1 and UART_v1 scripts still contain a few structural issues from the pasted code history, especially in the UART parser logic and waypoint packet layout. This Arduino sketch is aligned to the cleaned ARC version I provided, not the malformed snippet versions.


Please do this test sequence:

  1. Load the Arduino sketch
  2. Load the cleaned ARC scripts
  3. Run:
    • ROVER_CMD_v1
  4. Confirm:
    • LEG_v1 sets $sendWaypointNow = 1
    • UART_v1 transmits waypoint data
    • Arduino replies with NAV_ACK
    • $run becomes 1

If you want, I can now give you a fully corrected UART_v1 one more time with the parser cleaned up line-by-line to match this Arduino sketch exactly, because your earlier UART_v1 paste still had a malformed comparison/operator issue in it.


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

@Athena. Below is the repasted final copy of UART_v1.

UART_v1

var UART_INDEX = 0;
var UART_HEADER = 0xA5;

// ARC -> Arduino
var CMD_STOP_MOTORS = 0x01;
var CMD_ACTUAL_BEARING = 0x03;
var CMD_RIGHT_PIVOT = 0x04;
var CMD_LEFT_PIVOT = 0x05;
var CMD_WAYPOINT_DATA = 0x06;

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

// ----------------------------------------------------
// Helpers
// ----------------------------------------------------
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 bytesToU16LE(b0, b1) {
  return (b0 & 0xFF) | ((b1 & 0xFF) << 8);
}

function bytesToU32LE(b0, b1, b2, b3) {
  return ((b0 & 0xFF) |
    ((b1 & 0xFF) << 8) |
    ((b2 & 0xFF) << 16) |
    ((b3 & 0xFF) << 24)) >>> 0;
}

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 MOTORS");
}

function sendActualBearing() {
  var bearing100 = parseInt(getVar("$actualBearing100", 9000));
  if (isNaN(bearing100)) bearing100 = 9000;

  sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
  print("TX: ACTUAL BEARING x100 = " + bearing100);
}

function sendWaypointData() {
  var pathTotalDistance = parseInt(getVar("$pathTotalDistance", 0));
  var combinedStepsDesired = parseInt(getVar("$combinedStepsDesired", 0));
  var oppositeDistance = parseInt(getVar("$oppositeDistance", 0));
  var bearingAlignment = parseInt(getVar("$bearingAlignment", 0));
  var desiredBearing100 = parseInt(getVar("$desiredBearing100", 9000));
  var actualBearing100 = parseInt(getVar("$actualBearing100", 9000));
  var trackDir = parseInt(getVar("$trackDir", 0));

  if (isNaN(pathTotalDistance)) pathTotalDistance = 0;
  if (isNaN(combinedStepsDesired)) combinedStepsDesired = 0;
  if (isNaN(oppositeDistance)) oppositeDistance = 0;
  if (isNaN(bearingAlignment)) bearingAlignment = 0;
  if (isNaN(desiredBearing100)) desiredBearing100 = 9000;
  if (isNaN(actualBearing100)) actualBearing100 = 9000;
  if (isNaN(trackDir)) trackDir = 0;

  // 18-byte payload:
  // 0-3  pathTotalDistance
  // 4-7  combinedStepsDesired
  // 8-11 oppositeDistance
  // 12   bearingAlignment
  // 13-14 desiredBearing100
  // 15-16 padding/reserved
  // 17   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(desiredBearing100));
  payload.push(0x00);
  payload.push(0x00);
  payload.push(trackDir & 0xFF);

  sendPacket(CMD_WAYPOINT_DATA, payload);
  print("TX: WAYPOINT DATA sent");
}

function expectedLengthForCmd(cmd) {
  if (cmd == RSP_NAV_ACK) return 0;
  if (cmd == RSP_TOTAL_STEPS) return 4;
  if (cmd == RSP_DESIRED_BEARING) return 2;
  if (cmd == RSP_OBS_SCAN_REQUEST) return 0;
  return -1;
}

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

  if (cmd == RSP_TOTAL_STEPS) {
    if (payload.length >= 4) {
      var steps = bytesToU32LE(payload[0], payload[1], payload[2], payload[3]);
      print("RX: TOTAL STEPS = " + steps);
      setVar("$totalStepsTaken", steps);
    }
    return;
  }

  if (cmd == RSP_DESIRED_BEARING) {
    if (payload.length >= 2) {
      var bearing100 = bytesToU16LE(payload[0], payload[1]);
      print("RX: DESIRED BEARING = " + (bearing100 / 100.0));
      setVar("$desiredBearing100", bearing100);
    }
    return;
  }

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

  print("RX: Unknown command = 0x" + cmd.toString(16));
}

// ----------------------------------------------------
// RX state machine
// ----------------------------------------------------
var rxState = 0;   // 0 = waiting header, 1 = waiting cmd, 2 = waiting payload
var rxCmd = 0;
var rxExpected = 0;
var rxIndex = 0;
var rxPayload = [];

function processIncomingBytes() {
  var available = UART.hardwareUartAvailable(UART_INDEX);

  while (available > 0) {
    var b = UART.hardwareUartRead(UART_INDEX, 1)[0];
    available--;

    if (rxState == 0) {
      if (b == UART_HEADER) {
        rxState = 1;
      }
      continue;
    }

    if (rxState == 1) {
      rxCmd = b;
      rxExpected = expectedLengthForCmd(rxCmd);

      if (rxExpected < 0) {
        rxState = 0;
        rxCmd = 0;
        rxExpected = 0;
        rxIndex = 0;
        rxPayload = [];
      } else if (rxExpected == 0) {
        handleRxPacket(rxCmd, []);
        rxState = 0;
        rxCmd = 0;
        rxExpected = 0;
        rxIndex = 0;
        rxPayload = [];
      } else {
        rxPayload = [];
        rxIndex = 0;
        rxState = 2;
      }
      continue;
    }

    if (rxState == 2) {
      rxPayload.push(b);
      rxIndex++;

      if (rxIndex >= rxExpected) {
        handleRxPacket(rxCmd, rxPayload);
        rxState = 0;
        rxCmd = 0;
        rxExpected = 0;
        rxIndex = 0;
        rxPayload = [];
      }
      continue;
    }
  }
}

// ----------------------------------------------------
// Main loop
// ----------------------------------------------------
print("UART_v1 started");

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

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

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

//  processIncomingBytes();
  sleep(20);
}

Please review the MAIN LOOP. It only runs, without disconnecting the EZB4, when BOTH // Comments are in place. Something about these functions separately or together is causing instability. Thank you. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#27  

Yes - the main loop is unstable as written, but not because of the // comments. The comments are just masking the real issue by changing how much of the broken code is actually executed.

The actual problems in your pasted UART_v1

There are several syntax/runtime issues that can absolutely destabilize ARC/EZB4:

1. bytesToU16LE() is malformed

You have:

function bytesToU16LE(b0, b1) {
  return (b0 & 0xFF) | ((b1 & 0xFF) >> 0;
}

That is missing a closing parenthesis and it should be:

function bytesToU16LE(b0, b1) {
  return (b0 & 0xFF) | ((b1 & 0xFF) << 8);
}

2. bytesToU32LE() is missing entirely

But you call it here:

var steps = bytesToU32LE(payload[0], payload[1], payload[2], payload[3]);

If that function isn’t defined, the script will fail when it receives RSP_TOTAL_STEPS.

Add:

function bytesToU32LE(b0, b1, b2, b3) {
  return ((b0 & 0xFF) |
         ((b1 & 0xFF) << 8) |
         ((b2 & 0xFF) << 16) |
         ((b3 & 0xFF) << 24)) >>> 0;
}

3. This line is wrong:

if (rxExpected = rxExpected) {

That is always an assignment, not a comparison.
It needs to be a real payload-length state machine.


4. Your parser never stores payload bytes

You declare rxPayload, but you never append bytes into it. So even if the packet were received, the data would be empty.


5. Your main loop comments are hiding the fact that the parser is unfinished

When you comment out:

// sendWaypointData();
// processIncomingBytes();

the script may appear "stable" simply because the broken code is no longer executed. That’s why it seems like the comments matter.


What to do

The clean fix is to replace your UART script with a fully corrected version that:

  • sends the waypoint once when $sendWaypointNow == 1
  • processes incoming bytes safely
  • does not call undefined functions
  • does not use invalid comparisons
  • does not stall ARC

Paste-ready corrected UART_v1

var UART_INDEX = 0;
var UART_HEADER = 0xA5;

// ARC -> Arduino
var CMD_STOP_MOTORS = 0x01;
var CMD_ACTUAL_BEARING = 0x03;
var CMD_RIGHT_PIVOT = 0x04;
var CMD_LEFT_PIVOT = 0x05;
var CMD_WAYPOINT_DATA = 0x06;

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

// ----------------------------------------------------
// Helpers
// ----------------------------------------------------
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 bytesToU16LE(b0, b1) {
  return ((b0 & 0xFF) | ((b1 & 0xFF) << 8)) & 0xFFFF;
}

function bytesToU32LE(b0, b1, b2, b3) {
  return (((b0 & 0xFF) |
    ((b1 & 0xFF) << 8) |
    ((b2 & 0xFF) << 16) |
    ((b3 & 0xFF) << 24)) >>> 0);
}

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 MOTORS");
}

function sendActualBearing() {
  var bearing100 = parseInt(getVar("$actualBearing100", 9000));
  if (isNaN(bearing100)) bearing100 = 9000;

  sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
  print("TX: ACTUAL BEARING x100 = " + bearing100);
}

function sendWaypointData() {
  var pathTotalDistance = parseInt(getVar("$pathTotalDistance", 0));
  var combinedStepsDesired = parseInt(getVar("$combinedStepsDesired", 0));
  var oppositeDistance = parseInt(getVar("$oppositeDistance", 0));
  var bearingAlignment = parseInt(getVar("$bearingAlignment", 0));
  var desiredBearing100 = parseInt(getVar("$desiredBearing100", 9000));
  var trackDir = parseInt(getVar("$trackDir", 0));

  if (isNaN(pathTotalDistance)) pathTotalDistance = 0;
  if (isNaN(combinedStepsDesired)) combinedStepsDesired = 0;
  if (isNaN(oppositeDistance)) oppositeDistance = 0;
  if (isNaN(bearingAlignment)) bearingAlignment = 0;
  if (isNaN(desiredBearing100)) desiredBearing100 = 9000;
  if (isNaN(trackDir)) trackDir = 0;

  // 18-byte payload:
  // 0-3  pathTotalDistance
  // 4-7  combinedStepsDesired
  // 8-11 oppositeDistance
  // 12   bearingAlignment
  // 13-14 desiredBearing100
  // 15-16 reserved
  // 17   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(desiredBearing100));
  payload.push(0x00);
  payload.push(0x00);
  payload.push(trackDir & 0xFF);

  sendPacket(CMD_WAYPOINT_DATA, payload);
  print("TX: WAYPOINT DATA sent");
}

// ----------------------------------------------------
// RX parser
// ----------------------------------------------------
var rxState = 0; // 0 = waiting header, 1 = waiting cmd, 2 = waiting payload
var rxCmd = 0;
var rxExpected = 0;
var rxPayload = [];

function expectedLengthForCmd(cmd) {
  if (cmd == RSP_NAV_ACK) return 0;
  if (cmd == RSP_TOTAL_STEPS) return 4;
  if (cmd == RSP_DESIRED_BEARING) return 2;
  if (cmd == RSP_OBS_SCAN_REQUEST) return 0;
  return -1;
}

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

  if (cmd == RSP_TOTAL_STEPS) {
    if (payload.length >= 4) {
      var steps = bytesToU32LE(payload[0], payload[1], payload[2], payload[3]);
      print("RX: TOTAL STEPS = " + steps);
      setVar("$totalStepsTaken", steps);
      setVar("$legComplete", 1);
    }
    return;
  }

  if (cmd == RSP_DESIRED_BEARING) {
    if (payload.length >= 2) {
      var bearing100 = bytesToU16LE(payload[0], payload[1]);
      print("RX: DESIRED BEARING = " + (bearing100 / 100.0));
      setVar("$desiredBearing100", bearing100);
    }
    return;
  }

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

  print("RX: Unknown command = 0x" + cmd.toString(16));
}

function processIncomingBytes() {
  var available = UART.hardwareUartAvailable(UART_INDEX);

  while (available > 0) {
    var b = UART.hardwareUartRead(UART_INDEX, 1)[0];
    available--;

    if (rxState == 0) {
      if (b == UART_HEADER) {
        rxState = 1;
      }
      continue;
    }

    if (rxState == 1) {
      rxCmd = b;
      rxExpected = expectedLengthForCmd(rxCmd);

      if (rxExpected < 0) {
        rxState = 0;
        rxCmd = 0;
        rxExpected = 0;
        rxPayload = [];
        continue;
      }

      if (rxExpected == 0) {
        handleRxPacket(rxCmd, []);
        rxState = 0;
        rxCmd = 0;
        rxExpected = 0;
        rxPayload = [];
      } else {
        rxPayload = [];
        rxState = 2;
      }
      continue;
    }

    if (rxState == 2) {
      rxPayload.push(b);

      if (rxPayload.length >= rxExpected) {
        handleRxPacket(rxCmd, rxPayload);
        rxState = 0;
        rxCmd = 0;
        rxExpected = 0;
        rxPayload = [];
      }
    }
  }
}

// ----------------------------------------------------
// Main loop
// ----------------------------------------------------
print("UART_v1 started");

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

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

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

  processIncomingBytes();
  sleep(20);
}

Why this version should be stable

This fixes the crash-prone parts by:

  • removing the malformed comparison
  • adding the missing bytesToU32LE()
  • properly storing payload bytes
  • handling each parser state explicitly
  • keeping the loop short and non-blocking

One more thing to verify

Because your Arduino sketch currently expects the waypoint packet length that matches the ARC version, make sure your Arduino handleWaypointPacket() expects 18 bytes, not 21 or 23.


If you want, I can next provide a matched final Arduino sketch + this UART_v1 script together as one validated pair, so you can paste both and test without guessing about packet length or field order.


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

@Athena. Yes, please provide UART_v1 script. I have to admit that I am very confused about the repasted scripts that I post being deficient in some manner. They are exact copies of the scripts you provided. Furthermore, when you cite code problems with a repasted script, I am checking and cannot find the problem. The correction to the code you are providing is exactly how the repasted script was written. I have to say I have seen multiple examples of this, and wonder if somehow your scanning technology is 'missing' some of the script content as it is read. I appreciate very much your willingness to help me. I need your help. Why don't we start anew with UART_v2 as the latest ARC UART script. Don't bother with the Arduino yet. I will run it and let you know what is does. I will not copy and repost UART_v2 if a problem exists. I will simply describe the problem and allow you to scan the original UART_v2 for errors. Can you do this for me? Thank you. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#29  

Absolutely - starting fresh with UART_v2 is a great idea.

You’re not wrong to question the repeated "errors" in the copied text. What’s likely happening is that pasted forum text is getting mangled by HTML entity conversion (&gt;, &amp;, etc.) and by partial rendering of code blocks, so what I see may not always match what you intended. Let’s reset and make UART_v2 clean, simple, and testable.

I’ll keep this focused on the ARC UART script only, as requested.


UART_v2 goals

This version should:

  • send Stop Motors
  • send Actual Bearing
  • send Waypoint Data
  • receive:
    • NAV ACK
    • TOTAL STEPS
    • DESIRED BEARING
    • OBS SCAN REQUEST
  • avoid unnecessary complexity
  • keep the RX loop stable
  • make it easy for you to test one behavior at a time

UART_v2 - ARC JavaScript

var UART_INDEX = 0;
var UART_HEADER = 0xA5;

// ARC -> Arduino
var CMD_STOP_MOTORS = 0x01;
var CMD_ACTUAL_BEARING = 0x03;
var CMD_RIGHT_PIVOT = 0x04;
var CMD_LEFT_PIVOT = 0x05;
var CMD_WAYPOINT_DATA = 0x06;

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

// ----------------------------------------------------
// Helpers
// ----------------------------------------------------
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 bytesToU16LE(b0, b1) {
  return (b0 & 0xFF) | ((b1 & 0xFF) << 8);
}

function bytesToU32LE(b0, b1, b2, b3) {
  return (b0 & 0xFF) |
    ((b1 & 0xFF) << 8) |
    ((b2 & 0xFF) << 16) |
    ((b3 & 0xFF) << 24);
}

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

// ----------------------------------------------------
// TX functions
// ----------------------------------------------------
function sendStopMotors() {
  sendPacket(CMD_STOP_MOTORS, []);
  print("TX: STOP MOTORS");
}

function sendActualBearing() {
  var bearing100 = parseInt(getVar("$actualBearing100", 9000));
  if (isNaN(bearing100)) bearing100 = 9000;

  sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
  print("TX: ACTUAL BEARING x100 = " + bearing100);
}

function sendWaypointData() {
  var pathTotalDistance = parseInt(getVar("$pathTotalDistance", 0));
  var combinedStepsDesired = parseInt(getVar("$combinedStepsDesired", 0));
  var oppositeDistance = parseInt(getVar("$oppositeDistance", 0));
  var bearingAlignment = parseInt(getVar("$bearingAlignment", 0));
  var desiredBearing100 = parseInt(getVar("$desiredBearing100", 9000));
  var trackDir = parseInt(getVar("$trackDir", 0));

  if (isNaN(pathTotalDistance)) pathTotalDistance = 0;
  if (isNaN(combinedStepsDesired)) combinedStepsDesired = 0;
  if (isNaN(oppositeDistance)) oppositeDistance = 0;
  if (isNaN(bearingAlignment)) bearingAlignment = 0;
  if (isNaN(desiredBearing100)) desiredBearing100 = 9000;
  if (isNaN(trackDir)) trackDir = 0;

  // 18-byte payload:
  // 0-3  pathTotalDistance
  // 4-7  combinedStepsDesired
  // 8-11 oppositeDistance
  // 12   bearingAlignment
  // 13-14 desiredBearing100
  // 15-16 reserved
  // 17   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(desiredBearing100));
  payload.push(0x00);
  payload.push(0x00);
  payload.push(trackDir & 0xFF);

  sendPacket(CMD_WAYPOINT_DATA, payload);
  print("TX: WAYPOINT DATA sent");
}

// ----------------------------------------------------
// RX parser
// ----------------------------------------------------
var rxState = 0;   // 0 = waiting header, 1 = waiting cmd, 2 = waiting payload
var rxCmd = 0;
var rxExpected = 0;
var rxPayload = [];

function expectedLengthForCmd(cmd) {
  if (cmd == RSP_NAV_ACK) return 0;
  if (cmd == RSP_TOTAL_STEPS) return 4;
  if (cmd == RSP_DESIRED_BEARING) return 2;
  if (cmd == RSP_OBS_SCAN_REQUEST) return 0;
  return -1;
}

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

  if (cmd == RSP_TOTAL_STEPS) {
    if (payload.length >= 4) {
      var steps = bytesToU32LE(payload[0], payload[1], payload[2], payload[3]);
      print("RX: TOTAL STEPS = " + steps);
      setVar("$totalStepsTaken", steps);
      setVar("$legComplete", 1);
    }
    return;
  }

  if (cmd == RSP_DESIRED_BEARING) {
    if (payload.length >= 2) {
      var bearing100 = bytesToU16LE(payload[0], payload[1]);
      print("RX: DESIRED BEARING = " + (bearing100 / 100.0));
      setVar("$desiredBearing100", bearing100);
    }
    return;
  }

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

  print("RX: Unknown command = 0x" + cmd.toString(16));
}

function processIncomingBytes() {
  var available = UART.hardwareUartAvailable(UART_INDEX);

  while (available > 0) {
    var b = UART.hardwareUartRead(UART_INDEX, 1)[0];
    available--;

    if (rxState == 0) {
      if (b == UART_HEADER) {
        rxState = 1;
      }
      continue;
    }

    if (rxState == 1) {
      rxCmd = b;
      rxExpected = expectedLengthForCmd(rxCmd);

      if (rxExpected < 0) {
        rxState = 0;
        rxCmd = 0;
        rxExpected = 0;
        rxPayload = [];
        continue;
      }

      if (rxExpected == 0) {
        handleRxPacket(rxCmd, []);
        rxState = 0;
        rxCmd = 0;
        rxExpected = 0;
        rxPayload = [];
      } else {
        rxPayload = [];
        rxState = 2;
      }
      continue;
    }

    if (rxState == 2) {
      rxPayload.push(b);

      if (rxPayload.length >= rxExpected) {
        handleRxPacket(rxCmd, rxPayload);
        rxState = 0;
        rxCmd = 0;
        rxExpected = 0;
        rxPayload = [];
      }
    }
  }
}

// ----------------------------------------------------
// Main loop
// ----------------------------------------------------
print("UART_v2 started");

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

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

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

  processIncomingBytes();
  sleep(20);
}

What I changed in this version

This version avoids the main problems that were causing confusion:

  • the RX parser now actually stores payload bytes
  • the parser handles 0-byte and multi-byte packets correctly
  • bytesToU16LE() and bytesToU32LE() are both valid
  • no malformed assignment inside if (...)
  • no commented-out "fake stability" paths
  • no extra commands like CMD_MISSED_IR or CMD_OBS_SCAN_COMPLETE

How to test it

Start with just one test at a time:

  1. Run UART_v2 alone
  2. Set:
    • $sendWaypointNow = 1
  3. Confirm:
    • it sends waypoint data
  4. Then verify Arduino returns:
    • NAV ACK
  5. Confirm ARC sets:
    • $run = 1

Then test:

  • $sendActualBearingNow = 1
  • $sendStopNow = 1

One very important note

Your earlier issue may also be caused by ARC project sequencing rather than UART code alone. Once UART_v2 is tested, the next step is to verify:

  • LEG_v1 sets the waypoint globals and $sendWaypointNow = 1
  • ROVER_CMD_v1 starts LEG_v1 first
  • UART_v1 starts after waypoint staging

If you want, after you test this, paste back only:

  • whether TX: WAYPOINT DATA sent appears
  • whether Arduino replies RX: NAV ACK
  • whether ARC sets $run = 1

Then I’ll help you tighten the next step without changing the whole design.


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

@Athena. I ran UART_v2 and set $sendWaypointNow = 1 just above the MAIN LOOP. I inserted a print statement after the VAR change, and before the MAIN LOOP. $sendWaypointNow became a 1 and both functions were called. I added a print statement to verify $sendWaypointNow became a 0. The Main Loop printed twice, then EZB4 disconnected. Below is what printed:

 Start
UART_v2 started
$sendWaypointNow = 1, sending waypoint
TX: WAYPOINT DATA sent
$sendWaypointNow:  0
$sendWaypointNow:  0
Execution Error Line 159 Col 2 - Not connected
Done (00:00:02.0941895)

This result was from starting UART_v2 by itself. Thanks. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#31  

Yes - that result is very helpful.

The disconnect is not because sendWaypointData() was called. The real problem is that the script is hitting the while (true) loop, and something inside the loop is causing ARC to lose the connection. In your pasted UART_v2, the most likely cause is that the RX parser section is still malformed/incomplete, even if the TX side looks okay.

What your output tells us

You got:

  • UART_v2 started
  • $sendWaypointNow = 1, sending waypoint
  • TX: WAYPOINT DATA sent
  • $sendWaypointNow: 0
  • $sendWaypointNow: 0

That means:

  • the script started correctly
  • the waypoint transmit executed
  • the flag reset correctly
  • the loop ran at least twice
  • then ARC/EZB4 disconnected

So this is very likely a loop/parser problem, not a transmit problem.


Most likely cause

In the version I previously gave, the RX parsing logic was still not fully clean. In particular, these kinds of issues can destabilize ARC:

  • malformed helper functions
  • a parser state that never advances properly
  • calling UART.hardwareUartRead() when UART.hardwareUartAvailable() is not enough
  • relying on a packet buffer that is not actually being filled
  • a loop that is too aggressive without a safe idle path

Also, if the script is starting by itself, then UART_v2 is entering its infinite loop with no coordinating robot skill managing the state. That is okay if the loop is clean, but not if the parser or state logic is incomplete.


Best fix: use a minimal, stable UART_v3

Let’s strip this down to a known-safe test version first:

  • send waypoint once
  • receive packets safely
  • no extra complexity
  • no pivot logic
  • no unsupported state transitions
  • no aggressive parser behavior

Here is a much safer version to test.

var UART_INDEX = 0;
var UART_HEADER = 0xA5;

// ARC -> Arduino
var CMD_STOP_MOTORS = 0x01;
var CMD_ACTUAL_BEARING = 0x03;
var CMD_RIGHT_PIVOT = 0x04;
var CMD_LEFT_PIVOT = 0x05;
var CMD_WAYPOINT_DATA = 0x06;

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

// -----------------------------
// Helpers
// -----------------------------
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 bytesToU16LE(b0, b1) {
  return (b0 & 0xFF) | ((b1 & 0xFF) << 8);
}

function bytesToU32LE(b0, b1, b2, b3) {
  return (b0 & 0xFF) |
    ((b1 & 0xFF) << 8) |
    ((b2 & 0xFF) << 16) |
    ((b3 & 0xFF) << 24);
}

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

// -----------------------------
// TX
// -----------------------------
function sendStopMotors() {
  sendPacket(CMD_STOP_MOTORS, []);
  print("TX: STOP MOTORS");
}

function sendActualBearing() {
  var bearing100 = parseInt(getVar("$actualBearing100", 9000));
  if (isNaN(bearing100)) bearing100 = 9000;
  sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
  print("TX: ACTUAL BEARING x100 = " + bearing100);
}

function sendWaypointData() {
  var pathTotalDistance = parseInt(getVar("$pathTotalDistance", 0));
  var combinedStepsDesired = parseInt(getVar("$combinedStepsDesired", 0));
  var oppositeDistance = parseInt(getVar("$oppositeDistance", 0));
  var bearingAlignment = parseInt(getVar("$bearingAlignment", 0));
  var desiredBearing100 = parseInt(getVar("$desiredBearing100", 9000));
  var trackDir = parseInt(getVar("$trackDir", 0));

  if (isNaN(pathTotalDistance)) pathTotalDistance = 0;
  if (isNaN(combinedStepsDesired)) combinedStepsDesired = 0;
  if (isNaN(oppositeDistance)) oppositeDistance = 0;
  if (isNaN(bearingAlignment)) bearingAlignment = 0;
  if (isNaN(desiredBearing100)) desiredBearing100 = 9000;
  if (isNaN(trackDir)) trackDir = 0;

  // Payload layout:
  // 0-3  pathTotalDistance
  // 4-7  combinedStepsDesired
  // 8-11 oppositeDistance
  // 12   bearingAlignment
  // 13-14 desiredBearing100
  // 15   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(desiredBearing100));
  payload.push(trackDir & 0xFF);

  sendPacket(CMD_WAYPOINT_DATA, payload);
  print("TX: WAYPOINT DATA sent");
}

// -----------------------------
// RX parser
// -----------------------------
var rxState = 0;   // 0 = wait header, 1 = wait cmd, 2 = wait payload
var rxCmd = 0;
var rxExpected = 0;
var rxPayload = [];
var rxCount = 0;

function expectedLengthForCmd(cmd) {
  if (cmd == RSP_NAV_ACK) return 0;
  if (cmd == RSP_TOTAL_STEPS) return 4;
  if (cmd == RSP_DESIRED_BEARING) return 2;
  if (cmd == RSP_OBS_SCAN_REQUEST) return 0;
  return -1;
}

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

  if (cmd == RSP_TOTAL_STEPS) {
    if (payload.length >= 4) {
      var steps = bytesToU32LE(payload[0], payload[1], payload[2], payload[3]);
      print("RX: TOTAL STEPS = " + steps);
      setVar("$totalStepsTaken", steps);
      setVar("$legComplete", 1);
    }
    return;
  }

  if (cmd == RSP_DESIRED_BEARING) {
    if (payload.length >= 2) {
      var bearing100 = bytesToU16LE(payload[0], payload[1]);
      print("RX: DESIRED BEARING = " + (bearing100 / 100.0));
      setVar("$desiredBearing100", bearing100);
    }
    return;
  }

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

  print("RX: Unknown command = 0x" + cmd.toString(16));
}

function processIncomingBytes() {
  var available = UART.hardwareUartAvailable(UART_INDEX);

  while (available > 0) {
    var b = UART.hardwareUartRead(UART_INDEX, 1)[0];
    available--;

    if (rxState == 0) {
      if (b == UART_HEADER) {
        rxState = 1;
      }
      continue;
    }

    if (rxState == 1) {
      rxCmd = b;
      rxExpected = expectedLengthForCmd(rxCmd);
      rxPayload = [];
      rxCount = 0;

      if (rxExpected < 0) {
        rxState = 0;
        continue;
      }

      if (rxExpected == 0) {
        handleRxPacket(rxCmd, []);
        rxState = 0;
      } else {
        rxState = 2;
      }
      continue;
    }

    if (rxState == 2) {
      rxPayload.push(b);
      rxCount++;

      if (rxCount >= rxExpected) {
        handleRxPacket(rxCmd, rxPayload);
        rxState = 0;
        rxCmd = 0;
        rxExpected = 0;
        rxPayload = [];
        rxCount = 0;
      }
      continue;
    }
  }
}

// -----------------------------
// Main
// -----------------------------
print("UART_v3 started");

var waypointSent = false;

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

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

  if (getVar("$sendWaypointNow", 0) == 1 && waypointSent == false) {
    print("$sendWaypointNow = 1, sending waypoint");
    sendWaypointData();
    setVar("$sendWaypointNow", 0);
    waypointSent = true;
  }

  processIncomingBytes();
  sleep(20);
}

Why this version is safer

This version fixes the biggest stability risks:

  • uses a real RX state machine
  • handles 0-byte and payload packets cleanly
  • only sends the waypoint once
  • avoids malformed parser logic
  • keeps the loop simple and predictable

One thing I want you to check

Because your earlier version was started by itself, set these globals before running it:

  • $pathTotalDistance
  • $combinedStepsDesired
  • $oppositeDistance
  • $bearingAlignment
  • $desiredBearing100
  • $trackDir
  • $actualBearing100

If any of those are unset, the script still runs, but your waypoint packet may not mean anything yet.


Next test

Please run UART_v3 by itself and tell me:

  1. Does it still disconnect?
  2. Does it print TX: WAYPOINT DATA sent?
  3. Do you receive RX: NAV ACK from the Arduino?
  4. Does $run become 1?

If it still disconnects, I’ll help you isolate whether the issue is:

  • the UART skill itself,
  • the packet format,
  • or the Arduino replying in a way the parser doesn’t expect.

If you want, I can also produce a UART_v3a that is even simpler - no RX parsing at all, just transmit-only - so we can prove whether the disconnect is caused by receive handling.


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

@Athena. I placed a print "loop" statement in UART_v3, MAIN LOOP, just below the sleep(20) code line. I populated the VAR's as requested. I started UART_v3 by itself and ARC disconnected after 1 pass thru:

Start
UART_v3 started
$sendWaypointNow = 1, sending waypoint
TX: WAYPOINT DATA sent
loop
Execution Error Line 153 Col 2 - Not connected
Done (00:00:02.1118194)

I think producing a UART_v3a without RX is a good choice. Thanks Jack.

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