Roll Pitch Yaw icon Roll Pitch Yaw — Rotate and position camera or object with roll/pitch/yaw controls; save camera settings; bind or monitor a 3-value EZ-Script array for live rotation. Try it →
Asked — Edited
Resolved Resolved by Athena!
1 Participant
Mega UART Protocol Troubleshooting

Mega UART Protocol Troubleshooting

@Athena - please examine the Mega sketch and EZBv4 script below.

Summary / expected behavior

  • ARC project where ARC communicates with an Arduino Mega via an EZBv4 script over a hardware UART connection (EZB hardware UART Mega Serial3).
  • Mega firmware should run the motors when Waypoint Data (CMD_WAYPOINT_DATA) is received via UART, and it should print debug text when data arrives.

Observed behavior

  • After uploading the sketch to the Mega, the first Waypoint Data transmission is received and printed.
  • Subsequent UART transmissions are not received or printed until the sketch is re-uploaded to the Mega.
  • The motors never operate as expected when Waypoint Data is received.

Notes / context and things to verify

  • The Mega firmware uses Serial3 as the ARC/EZB link (#define LINK_SERIAL Serial3).
  • The EZBv4 manager script (Mega_Mgr_v3) sets UART_INDEX = 0 (hardware UART 0). Please verify the physical UART mapping between the EZB and the Mega (which EZB hardware UART is wired to the Mega's Serial3) and confirm the correct UART_INDEX to use in the EZBv4 script.
  • The Mega sketch parses binary packets framed with a UART header 0xA5 and a cmd/len/payload/checksum (checksum = XOR of cmd ^ len ^ payload bytes). Please verify that the EZB manager script’s packet formation and checksum (XOR of cmd/len/payload) matches the Mega firmware protocol exactly (including the header byte and byte ordering).
  • I have included both the Mega sketch (Mega_Firmware_v3) and the EZBv4 UART manager script (Mega_Mgr_v3) below.
  • Request: Can you troubleshoot the Mega firmware and rewrite it as necessary? Please name the revised firmware Mega_Firmware_v4.
  • Also verify the EZBv4 manager script matches the Mega protocol and rewrite it if necessary.

Thank you,
Jack

Mega_Firmware_v3

#include <AccelStepper.h>

// ======================================================
// UART / Protocol
// ======================================================
static const uint32_t BAUD = 115200;
static const uint8_t UART_HEADER = 0xA5;

// ARC/EZB link on Mega Serial3
#define LINK_SERIAL Serial3

// Debug to USB serial monitor
#define DBG_SERIAL Serial

// Commands from ARC/EZB
enum CommandId : uint8_t {
  CMD_STOP_MOTORS   = 0x01,
  CMD_ACTUAL_BEARING = 0x03,
  CMD_RIGHT_PIVOT   = 0x04,
  CMD_LEFT_PIVOT    = 0x05,
  CMD_WAYPOINT_DATA = 0x06
};

// Responses to ARC/EZB
enum ResponseId : uint8_t {
  RSP_NAV_ACK          = 0x10,
  RSP_TOTAL_STEPS      = 0x11,
  RSP_DESIRED_BEARING  = 0x12,
  RSP_OBS_SCAN_REQUEST = 0x13,
  RSP_READY            = 0x14,
  RSP_STATUS           = 0x15,
  RSP_ACK              = 0x16,
  RSP_NAK              = 0x17
};

// ======================================================
// Stepper motors
// ======================================================
AccelStepper LeftFrontWheel(AccelStepper::DRIVER, 3, 6);
AccelStepper LeftBackWheel(AccelStepper::DRIVER, 2, 5);
AccelStepper RightFrontWheel(AccelStepper::DRIVER, 12, 13);
AccelStepper RightBackWheel(AccelStepper::DRIVER, 4, 7);

// ======================================================
// State
// ======================================================
enum MotionMode {
  MODE_IDLE = 0,
  MODE_TRACKING = 1,
  MODE_PIVOT_RIGHT = 2,
  MODE_PIVOT_LEFT = 3
};

MotionMode mode = MODE_IDLE;
bool systemReady = false;
bool motorsEnabled = false;

// ======================================================
// Leg data
// ======================================================
uint32_t pathTotalDistance = 0;
uint32_t combinedStepsDesired = 0;
uint32_t oppositeDistance = 0;
uint8_t bearingAlignment = 0;
uint16_t desiredBearing100 = 9000;
uint16_t actualBearing100 = 9000;
uint8_t trackDir = 0;
uint32_t scanIntervalSteps = 3056;

// ======================================================
// Runtime
// ======================================================
uint32_t totalStepsTaken = 0;
uint32_t lastReportedSteps = 0;
uint32_t lastDesiredBearingReportMs = 0;
uint32_t lastStatusReportMs = 0;

// ======================================================
// Motion control
// ======================================================
int wheelSpeedBase = 1600;
int leftTargetSpeed = 0;
int rightTargetSpeed = 0;
int leftCurrentSpeed = 0;
int rightCurrentSpeed = 0;
int speedRampStep = 25;

float desiredCourse = 90.0f;
float actualCourse = 90.0f;
float diffCourse = 0.0f;

bool legInfoPrinted = false;
bool actualBearingPrintedOnce = false;
bool stopPrintedOnce = false;
bool waypointLoaded = false;

// ======================================================
// RX parser
// ======================================================
uint8_t rxState = 0;
uint8_t rxCmd = 0;
uint8_t rxLen = 0;
uint8_t rxIndex = 0;
uint8_t rxChecksum = 0;
uint8_t rxPayload[64];

// ======================================================
// Helpers
// ======================================================
void debugPrint(const String& s) {
  DBG_SERIAL.println(s);
}

void sendText(const String& s) {
  LINK_SERIAL.println(s);
}

void sendReady() {
  sendText("READY,Mega,v3");
}

void sendAck(const String& msg) {
  sendText("ACK," + msg);
}

void sendNak(const String& msg) {
  sendText("NAK," + msg);
}

uint8_t bytesToU16LE(const uint8_t* b) {
  return 0;
}

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

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

void sendBinaryPacket(uint8_t cmd, const uint8_t* payload, uint8_t len) {
  uint8_t cs = cmd ^ len;
  for (uint8_t i = 0; i < len; i++) cs ^= payload[i];

  LINK_SERIAL.write(UART_HEADER);
  LINK_SERIAL.write(cmd);
  LINK_SERIAL.write(len);
  for (uint8_t i = 0; i < len; i++) {
    LINK_SERIAL.write(payload[i]);
  }
  LINK_SERIAL.write(cs);
}

void printLegInfoOnce() {
  if (legInfoPrinted) return;

  debugPrint("MEGA leg info received:");
  debugPrint("  pathTotalDistance = " + String(pathTotalDistance));
  debugPrint("  combinedStepsDesired = " + String(combinedStepsDesired));
  debugPrint("  oppositeDistance = " + String(oppositeDistance));
  debugPrint("  bearingAlignment = " + String(bearingAlignment));
  debugPrint("  desiredBearing100 = " + String(desiredBearing100));
  debugPrint("  actualBearing100 = " + String(actualBearing100));
  debugPrint("  trackDir = " + String(trackDir));
  debugPrint("  scanIntervalSteps = " + String(scanIntervalSteps));

  legInfoPrinted = true;
}

void printActualBearingOnce(uint16_t bearing100) {
  if (!actualBearingPrintedOnce) {
    debugPrint("MEGA actual bearing received = " + String(bearing100));
    actualBearingPrintedOnce = true;
  }
}

void printStopOnce(const String& src) {
  if (!stopPrintedOnce) {
    debugPrint("MEGA STOP: " + src);
    stopPrintedOnce = true;
  }
}

void reportDesiredBearingIfChangedBy1(uint16_t newBearing100) {
  static int lastReportedDeg = -9999;
  int deg = (int)(newBearing100 / 100);

  if (lastReportedDeg == -9999 || abs(deg - lastReportedDeg) >= 1) {
    uint8_t payload[2];
    payload[0] = newBearing100 & 0xFF;
    payload[1] = (newBearing100 >> 8) & 0xFF;
    sendBinaryPacket(RSP_DESIRED_BEARING, payload, 2);

    debugPrint("MEGA desired bearing reported = " + String(newBearing100));
    lastReportedDeg = deg;
  }
}

void reportTotalStepsIfChanged() {
  if (totalStepsTaken != lastReportedSteps) {
    uint8_t payload[4];
    payload[0] = totalStepsTaken & 0xFF;
    payload[1] = (totalStepsTaken >> 8) & 0xFF;
    payload[2] = (totalStepsTaken >> 16) & 0xFF;
    payload[3] = (totalStepsTaken >> 24) & 0xFF;
    sendBinaryPacket(RSP_TOTAL_STEPS, payload, 4);
    lastReportedSteps = totalStepsTaken;
  }
}

void stopMotors() {
  leftTargetSpeed = 0;
  rightTargetSpeed = 0;
  leftCurrentSpeed = 0;
  rightCurrentSpeed = 0;
  motorsEnabled = false;
  mode = MODE_IDLE;

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

  LeftFrontWheel.runSpeed();
  LeftBackWheel.runSpeed();
  RightFrontWheel.runSpeed();
  RightBackWheel.runSpeed();
}

void applyMotorSpeeds() {
  LeftFrontWheel.setSpeed(leftCurrentSpeed);
  LeftBackWheel.setSpeed(rightCurrentSpeed);
  RightFrontWheel.setSpeed(rightCurrentSpeed);
  RightBackWheel.setSpeed(leftCurrentSpeed);

  LeftFrontWheel.runSpeed();
  LeftBackWheel.runSpeed();
  RightFrontWheel.runSpeed();
  RightBackWheel.runSpeed();
}

void rampTowardTargets() {
  if (leftCurrentSpeed < leftTargetSpeed) {
    leftCurrentSpeed += speedRampStep;
    if (leftCurrentSpeed > leftTargetSpeed) leftCurrentSpeed = leftTargetSpeed;
  } else if (leftCurrentSpeed > leftTargetSpeed) {
    leftCurrentSpeed -= speedRampStep;
    if (leftCurrentSpeed < leftTargetSpeed) leftCurrentSpeed = leftTargetSpeed;
  }

  if (rightCurrentSpeed < rightTargetSpeed) {
    rightCurrentSpeed += speedRampStep;
    if (rightCurrentSpeed > rightTargetSpeed) rightCurrentSpeed = rightTargetSpeed;
  } else if (rightCurrentSpeed > rightTargetSpeed) {
    rightCurrentSpeed -= speedRampStep;
    if (rightCurrentSpeed < rightTargetSpeed) rightCurrentSpeed = rightTargetSpeed;
  }
}

void computeDesiredCourse() {
  desiredCourse = desiredBearing100 / 100.0f;
}

void updateMotionControl() {
  if (!motorsEnabled) {
    stopMotors();
    return;
  }

  computeDesiredCourse();
  actualCourse = actualBearing100 / 100.0f;
  diffCourse = actualCourse - desiredCourse;

  int correction = (int)(diffCourse * 20.0f);

  leftTargetSpeed = wheelSpeedBase - correction;
  rightTargetSpeed = wheelSpeedBase + correction;

  if (leftTargetSpeed < wheelSpeedBase - 400) leftTargetSpeed = wheelSpeedBase - 400;
  if (leftTargetSpeed > wheelSpeedBase + 400) leftTargetSpeed = wheelSpeedBase + 400;
  if (rightTargetSpeed < wheelSpeedBase - 400) rightTargetSpeed = wheelSpeedBase - 400;
  if (rightTargetSpeed > wheelSpeedBase + 400) rightTargetSpeed = wheelSpeedBase + 400;

  rampTowardTargets();
  applyMotorSpeeds();
}

void handleWaypointPacket(const uint8_t* p, uint8_t len) {
  if (len != 22) {
    sendNak("WAYPOINT_LEN");
    return;
  }

  pathTotalDistance = readU32LE(&p[0]);
  combinedStepsDesired = readU32LE(&p[4]);
  oppositeDistance = readU32LE(&p[8]);
  bearingAlignment = p[12];
  desiredBearing100 = readU16LE(&p[13]);
  actualBearing100 = readU16LE(&p[15]);
  trackDir = p[17];
  scanIntervalSteps = readU32LE(&p[18]);

  printLegInfoOnce();
  sendAck("WAYPOINT");
}

void handleBinaryPacket(uint8_t cmd, const uint8_t* payload, uint8_t len) {
  switch (cmd) {
    case CMD_STOP_MOTORS:
      printStopOnce("received STOP command");
      stopMotors();
      sendAck("STOP");
      break;

    case CMD_ACTUAL_BEARING:
      if (len != 2) {
        sendNak("ACTUAL_BEARING_LEN");
        return;
      }
      actualBearing100 = readU16LE(payload);
      printActualBearingOnce(actualBearing100);
      break;

    case CMD_RIGHT_PIVOT:
      if (len != 2) {
        sendNak("RIGHT_PIVOT_LEN");
        return;
      }
      combinedStepsDesired = readU16LE(payload);
      mode = MODE_PIVOT_RIGHT;
      motorsEnabled = true;
      sendAck("RIGHT_PIVOT");
      break;

    case CMD_LEFT_PIVOT:
      if (len != 2) {
        sendNak("LEFT_PIVOT_LEN");
        return;
      }
      combinedStepsDesired = readU16LE(payload);
      mode = MODE_PIVOT_LEFT;
      motorsEnabled = true;
      sendAck("LEFT_PIVOT");
      break;

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

    default:
      sendNak("UNKNOWN_CMD");
      break;
  }
}

void processIncoming() {
  while (LINK_SERIAL.available() > 0) {
    uint8_t b = (uint8_t)LINK_SERIAL.read();

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

    if (rxState == 1) {
      rxCmd = b;
      rxChecksum = rxCmd;
      rxState = 2;
      continue;
    }

    if (rxState == 2) {
      rxLen = b;
      rxChecksum ^= rxLen;
      rxIndex = 0;
      if (rxLen > sizeof(rxPayload)) {
        rxState = 0;
        sendNak("LEN_OVF");
      } else {
        rxState = (rxLen == 0) ? 4 : 3;
      }
      continue;
    }

    if (rxState == 3) {
      rxPayload[rxIndex++] = b;
      rxChecksum ^= b;
      if (rxIndex >= rxLen) rxState = 4;
      continue;
    }

    if (rxState == 4) {
      if (b == rxChecksum) {
        handleBinaryPacket(rxCmd, rxPayload, rxLen);
      } else {
        sendNak("CS");
      }

      rxState = 0;
      rxCmd = 0;
      rxLen = 0;
      rxIndex = 0;
      rxChecksum = 0;
      continue;
    }
  }
}

// ======================================================
// Text handling
// ======================================================
String textLine = "";

void processTextLine(const String& line) {
  String s = line;
  s.trim();
  s.toUpperCase();

  if (s == "HELLO") {
    systemReady = true;
    sendReady();
    return;
  }

  if (s == "STATUS?") {
    String msg = "STATUS,";
    msg += (motorsEnabled ? "RUNNING" : "IDLE");
    msg += ",STEPS=" + String(totalStepsTaken);
    msg += ",MODE=" + String((int)mode);
    sendText(msg);
    return;
  }

  if (s == "START") {
    motorsEnabled = true;
    mode = MODE_TRACKING;
    sendAck("START");
    return;
  }

  if (s == "STOP") {
    printStopOnce("received text STOP");
    stopMotors();
    sendAck("STOP");
    return;
  }

  sendNak("UNKNOWN_TEXT");
}

void processTextSerial() {
  while (LINK_SERIAL.available() > 0) {
    char c = (char)LINK_SERIAL.read();
    if (c == '\n') {
      if (textLine.length() > 0) {
        processTextLine(textLine);
        textLine = "";
      }
    } else if (c != '\r') {
      textLine += c;
    }
  }
}

// ======================================================
// Setup / loop
// ======================================================
void setup() {
  DBG_SERIAL.begin(BAUD);
  LINK_SERIAL.begin(BAUD);

  LeftFrontWheel.setMaxSpeed(wheelSpeedBase * 2);
  LeftBackWheel.setMaxSpeed(wheelSpeedBase * 2);
  RightFrontWheel.setMaxSpeed(wheelSpeedBase * 2);
  RightBackWheel.setMaxSpeed(wheelSpeedBase * 2);

  sendReady();
  debugPrint("Mega v3 started");
}

void loop() {
  processIncoming();
  processTextSerial();

  if (motorsEnabled) {
    updateMotionControl();
    totalStepsTaken = (uint32_t)(abs(LeftFrontWheel.currentPosition()) + abs(RightFrontWheel.currentPosition()));
    reportTotalStepsIfChanged();
  } else {
    stopMotors();
  }

  if (millis() - lastDesiredBearingReportMs > 500) {
    reportDesiredBearingIfChangedBy1(desiredBearing100);
    lastDesiredBearingReportMs = millis();
  }

  delay(1);
}

EZB Mega_Mgr_v3

var UART_INDEX = 0; // Mega UART0
var UART_BAUD = 115200;
var UART_HEADER = 0xA5;

var running = true;
var megaReady = false;
var lastHelloMs = 0;
var lastStatusMs = 0;

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

// -------------------------
// Response IDs
// -------------------------
var RSP_NAV_ACK = 0x10;
var RSP_TOTAL_STEPS = 0x11;
var RSP_DESIRED_BEARING = 0x12;
var RSP_OBS_SCAN_REQUEST = 0x13;
var RSP_READY = 0x14;
var RSP_STATUS = 0x15;
var RSP_ACK = 0x16;
var RSP_NAK = 0x17;

// -------------------------
// RX parser
// -------------------------
var rxState = 0;
var rxCmd = 0;
var rxLen = 0;
var rxIndex = 0;
var rxPayload = [];
var rxChecksum = 0;

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

function clamp(v, min, max) {
  if (v < min) return min;
  if (v > max) return max;
  return v;
}

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 checksumXor(cmd, len, payload) {
  var c = (cmd ^ len) & 0xFF;
  for (var i = 0; i < payload.length; i++) {
    c ^= (payload[i] & 0xFF);
  }
  return c & 0xFF;
}

function sendPacket(cmd, payload) {
  var len = payload.length & 0xFF;
  var checksum = checksumXor(cmd, len, payload);

  UART.hardwareUartWrite(UART_INDEX, [UART_HEADER, cmd, len]);
  if (payload.length > 0) {
    UART.hardwareUartWrite(UART_INDEX, payload);
  }
  UART.hardwareUartWrite(UART_INDEX, [checksum]);
}

function requestHello() {
  // If your Mega firmware supports it, add a HELLO/READY handshake there too.
  // For now this is a logical startup marker in ARC.
  megaReady = false;
  lastHelloMs = (new Date()).getTime();
  print("TX: HELLO requested");
}

function requestStatus() {
  // If your Mega firmware supports STATUS? add a command ID for it.
  // For now this is a placeholder to match the Teensy manager style.
  lastStatusMs = (new Date()).getTime();
  print("TX: STATUS requested");
}

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

function sendActualBearing() {
  var bearing100 = safeInt(getVar("$actualBearing100", 9000), 9000);
  bearing100 = clamp(bearing100, 0, 18000);
  sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
  print("TX: ACTUAL BEARING x100 = " + bearing100);
}

function sendWaypointData() {
  var pathTotalDistance = safeInt(getVar("$pathTotalDistance", 0), 0) >>> 0;
  var combinedStepsDesired = safeInt(getVar("$combinedStepsDesired", 0), 0) >>> 0;
  var oppositeDistance = safeInt(getVar("$oppositeDistance", 0), 0) >>> 0;
  var bearingAlignment = safeInt(getVar("$bearingAlignment", 0), 0) & 0xFF;
  var trackDir = safeInt(getVar("$trackDir", 0), 0);
  trackDir = (trackDir === 0) ? 0 : 1;
  var desiredBearing100 = clamp(safeInt(getVar("$desiredBearing100", 9000), 9000), 0, 18000);
  var actualBearing100 = clamp(safeInt(getVar("$actualBearing100", 9000), 9000), 0, 18000);
  var scanIntervalSteps = safeInt(getVar("$scanIntervalSteps", 3056), 3056) >>> 0;

  setVar("$desiredBearing100", desiredBearing100);
  setVar("$actualBearing100", actualBearing100);

  var payload = [];
  payload = payload.concat(u32ToBytes(pathTotalDistance));
  payload = payload.concat(u32ToBytes(combinedStepsDesired));
  payload = payload.concat(u32ToBytes(oppositeDistance));
  payload.push(bearingAlignment);
  payload = payload.concat(u16ToBytes(desiredBearing100));
  payload = payload.concat(u16ToBytes(actualBearing100));
  payload.push(trackDir);
  payload = payload.concat(u32ToBytes(scanIntervalSteps));

  if (payload.length !== 22) {
    print("Waypoint payload length error: " + payload.length);
    return;
  }

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

function sendPivot(cmd, name) {
  var steps = safeInt(getVar("$pivotSteps", 0), 0) >>> 0;
  var payload = u16ToBytes(steps);
  sendPacket(cmd, payload);
  print("TX: " + name + " steps=" + steps);
}

function sendRightPivot() {
  sendPivot(CMD_RIGHT_PIVOT, "RIGHT PIVOT");
}

function sendLeftPivot() {
  sendPivot(CMD_LEFT_PIVOT, "LEFT PIVOT");
}

function requestObstacleScan() {
  if (getVar("$obsScanActive", 0) == 1) {
    print("RX: OBSTACLE SCAN REQUEST ignored (scan already active)");
    return;
  }
  setVar("$obsScanRequested", 1);
  setVar("$obsScanActive", 1);
  print("RX: OBSTACLE SCAN REQUEST -> $obsScanRequested=1, $obsScanActive=1");
}

function handleRxPacket(cmd, payload) {
  if (cmd == RSP_READY) {
    megaReady = true;
    print("RX: MEGA READY");
    return;
  }

  if (cmd == RSP_ACK) {
    print("RX: ACK");
    return;
  }

  if (cmd == RSP_NAK) {
    print("RX: NAK");
    return;
  }

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

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

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

  if (cmd == RSP_OBS_SCAN_REQUEST) {
    requestObstacleScan();
    return;
  }

  print("RX: Unknown cmd=" + cmd + " len=" + payload.length);
}

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

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

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

    if (rxState == 1) {
      rxCmd = b;
      rxChecksum = rxCmd;
      rxState = 2;
      continue;
    }

    if (rxState == 2) {
      rxLen = b;
      rxChecksum ^= rxLen;
      rxPayload = [];
      rxIndex = 0;
      rxState = (rxLen == 0) ? 4 : 3;
      continue;
    }

    if (rxState == 3) {
      rxPayload.push(b);
      rxChecksum ^= b;
      rxIndex++;
      if (rxIndex >= rxLen) rxState = 4;
      continue;
    }

    if (rxState == 4) {
      var cs = b & 0xFF;
      if (cs == (rxChecksum & 0xFF)) {
        handleRxPacket(rxCmd, rxPayload);
      } else {
        print("RX checksum error. cmd=" + rxCmd + " expected=" + (rxChecksum & 0xFF) + " got=" + cs);
      }

      rxState = 0;
      rxCmd = 0;
      rxLen = 0;
      rxIndex = 0;
      rxPayload = [];
      rxChecksum = 0;
      continue;
    }
  }
}

print("Mega UART Manager started");
UART.initHardwareUart(UART_INDEX, UART_BAUD);

requestHello();
sleep(200);
requestStatus();

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

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

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

  processIncomingBytes();
  sleep(20);
}


Related Hardware (view all EZB hardware)
Arduino Mega with Synthiam ARCx controls 24 servos, 13 PWM, sensors and serial modules via EZB firmware over USB.
USB
Servos 24
Camera
Audio
UART 3
I2C
ADC 8
Digital 21

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

Yes - your interpretation is correct.

1) The motorEnabled typo was my mistake

You fixed the right thing. In MEGA_FIRMWARE_v5, the state variable should be motorsEnabled everywhere. Thanks for catching that.


2) About the checksum error

A checksum error means the Mega manager received a packet whose computed checksum did not match the checksum byte that arrived at the end of the packet.

That does not mean ARC is sending a STOP command.

It means one of these happened:

  • the packet bytes got corrupted
  • a byte was missed
  • an extra byte was inserted
  • the parser got out of sync and interpreted a normal byte as the checksum byte

In your log:

RX checksum error. cmd=17 expected=65 got=38

That means:

  • it was parsing a packet with command 17 decimal = 0x11 = RSP_TOTAL_STEPS
  • it expected checksum 0x41
  • it received checksum 0x26

So yes, MEGA_FIRMWARE is sending step telemetry, and MEGA_MGR_v5 is trying to parse it.


3) Why this happened

You said this is not intended:

Almost all data within MEGA_FIRMWARE stays in MEGA_FIRMWARE until a trigger is hit.

That means the current RSP_TOTAL_STEPS traffic is too much.

So the fix is simple:

  • do not send total steps continuously
  • only send step-related packets when a trigger happens:
    • obstacle scan trigger
    • leg complete
    • stop
    • maybe periodic desired bearing updates

4) What MEGA_MGR should do

You described the manager perfectly:

  • it is a UART bridge
  • it should not compute or monitor anything
  • it should only forward packets between Mega and ARC

So I agree: MEGA_MGR_v5 should be simplified further.

Manager responsibilities:

  • ARC Mega:
    • WAYPOINT DATA
    • STOP
    • PIVOT
  • Mega ARC:
    • STOP
    • obstacle scan request
    • desired bearing updates
    • nav ack / ready / status
  • It should not process or print step spam
  • It should not count or react to total steps unless you explicitly want that later

5) What I recommend changing now

On the Mega side

Change MEGA_FIRMWARE_v5 so it:

  • keeps step counting local
  • does not emit RSP_TOTAL_STEPS continuously
  • only emits:
    • RSP_NAV_ACK
    • RSP_STATUS
    • RSP_DESIRED_BEARING
    • RSP_OBS_SCAN_REQUEST
    • RSP_ACK / RSP_NAK

On the manager side

Remove handling that expects step telemetry unless you really want it later.


MEGA_MGR_v6

Below is a simplified bridge-only manager. It forwards packets and does not try to monitor steps.

var UART_INDEX = 0;
var UART_BAUD = 115200;
var UART_HEADER = 0xA5;

// Command IDs
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;
var CMD_RESUME = 0x07;
var CMD_PAUSE = 0x08;

// Response IDs
var RSP_NAV_ACK = 0x10;
var RSP_TOTAL_STEPS = 0x11;
var RSP_DESIRED_BEARING = 0x12;
var RSP_OBS_SCAN_REQUEST = 0x13;
var RSP_READY = 0x14;
var RSP_STATUS = 0x15;
var RSP_ACK = 0x16;
var RSP_NAK = 0x17;

// RX parser
var rxState = 0;
var rxCmd = 0;
var rxLen = 0;
var rxIndex = 0;
var rxPayload = [];
var rxChecksum = 0;

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

function clamp(v, min, max) {
  if (v < min) return min;
  if (v > max) return max;
  return v;
}

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 checksum = cmd ^ payload.length;

  UART.hardwareUartWrite(UART_INDEX, [UART_HEADER]);
  UART.hardwareUartWrite(UART_INDEX, [cmd]);
  UART.hardwareUartWrite(UART_INDEX, [payload.length]);

  if (payload.length > 0) {
    UART.hardwareUartWrite(UART_INDEX, payload);
    for (var i = 0; i < payload.length; i++) {
      checksum ^= payload[i];
    }
  }

  UART.hardwareUartWrite(UART_INDEX, [checksum & 0xFF]);
}

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

function sendActualBearing() {
  var bearing100 = safeInt(getVar("$actualBearing100", 9000), 9000);
  bearing100 = clamp(bearing100, 0, 18000);
  sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
  print("TX: ACTUAL BEARING x100 = " + bearing100);
}

function sendWaypointData() {
  var pathTotalDistance = safeInt(getVar("$pathTotalDistance", 0), 0) >>> 0;
  var combinedStepsDesired = safeInt(getVar("$combinedStepsDesired", 0), 0) >>> 0;
  var oppositeDistance = safeInt(getVar("$oppositeDistance", 0), 0) >>> 0;
  var bearingAlignment = safeInt(getVar("$bearingAlignment", 0), 0) & 0xFF;
  var trackDir = safeInt(getVar("$trackDir", 0), 0) ? 1 : 0;
  var desiredBearing100 = clamp(safeInt(getVar("$desiredBearing100", 9000), 9000), 0, 18000);
  var actualBearing100 = clamp(safeInt(getVar("$actualBearing100", 9000), 9000), 0, 18000);
  var scanIntervalSteps = safeInt(getVar("$scanIntervalSteps", 3056), 3056) >>> 0;

  var payload = [];
  payload = payload.concat(u32ToBytes(pathTotalDistance));
  payload = payload.concat(u32ToBytes(combinedStepsDesired));
  payload = payload.concat(u32ToBytes(oppositeDistance));
  payload.push(bearingAlignment);
  payload = payload.concat(u16ToBytes(desiredBearing100));
  payload = payload.concat(u16ToBytes(actualBearing100));
  payload.push(trackDir);
  payload = payload.concat(u32ToBytes(scanIntervalSteps));

  if (payload.length !== 22) {
    print("Waypoint payload length error: " + payload.length);
    return;
  }

  setVar("$legComplete", 0);
  setVar("$navAckReceived", 0);

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

function sendPivot(cmd, name) {
  var steps = safeInt(getVar("$pivotSteps", 0), 0) >>> 0;
  sendPacket(cmd, u16ToBytes(steps));
  print("TX: " + name + " steps=" + steps);
}

function sendPause() {
  sendPacket(CMD_PAUSE, []);
  print("TX: PAUSE");
}

function sendResume() {
  sendPacket(CMD_RESUME, []);
  print("TX: RESUME");
}

function handleRxPacket(cmd, payload) {
  if (cmd == RSP_READY) {
    print("RX: READY");
    setVar("$megaReady", 1);
    return;
  }

  if (cmd == RSP_ACK) {
    print("RX: ACK");
    return;
  }

  if (cmd == RSP_NAK) {
    print("RX: NAK");
    return;
  }

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

  if (cmd == RSP_STATUS) {
    print("RX: STATUS");
    return;
  }

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

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

  // Ignore telemetry you don't need right now
}

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

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

    if (rxState == 1) {
      rxCmd = b;
      rxChecksum = rxCmd;
      rxState = 2;
      continue;
    }

    if (rxState == 2) {
      rxLen = b;
      rxChecksum ^= rxLen;
      rxPayload = [];
      rxIndex = 0;
      rxState = (rxLen == 0) ? 4 : 3;
      continue;
    }

    if (rxState == 3) {
      rxPayload.push(b);
      rxChecksum ^= b;
      rxIndex++;
      if (rxIndex >= rxLen) rxState = 4;
      continue;
    }

    if (rxState == 4) {
      var cs = b & 0xFF;
      if (cs == (rxChecksum & 0xFF)) {
        handleRxPacket(rxCmd, rxPayload);
      } else {
        print("RX checksum error. cmd=" + rxCmd + " expected=" + (rxChecksum & 0xFF) + " got=" + cs);
      }

      rxState = 0;
      rxCmd = 0;
      rxLen = 0;
      rxIndex = 0;
      rxPayload = [];
      rxChecksum = 0;
    }
  }
}

print("MEGA_MGR_v6 started");
UART.initHardwareUart(UART_INDEX, UART_BAUD);

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

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

  if (getVar("$sendPauseNow", 0) == 1) {
    sendPause();
    setVar("$sendPauseNow", 0);
  }

  if (getVar("$sendResumeNow", 0) == 1) {
    sendResume();
    setVar("$sendResumeNow", 0);
  }

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

  if (getVar("$sendRightPivotNow", 0) == 1) {
    sendPivot(CMD_RIGHT_PIVOT, "RIGHT PIVOT");
    setVar("$sendRightPivotNow", 0);
  }

  if (getVar("$sendLeftPivotNow", 0) == 1) {
    sendPivot(CMD_LEFT_PIVOT, "LEFT PIVOT");
    setVar("$sendLeftPivotNow", 0);
  }

  processIncomingBytes();
  sleep(20);
}

6) Answering your specific questions

1. Is MEGA_FIRMWARE_v5 sending number of steps as it loops to MEGA_MGR_v5?

Yes, it appears it was.
That is what caused the RSP_TOTAL_STEPS traffic and the checksum error messages.

If you want step count to remain local, we should remove that packet transmission except on special trigger events.

2. What is a checksum error and is it causing an early STOP?

A checksum error means the packet bytes did not match their checksum.
It is not a STOP command.

It can cause the manager to ignore the packet, but it should not itself stop the robot unless your script is explicitly reacting to it.


7) About the Teensy code you attached

Thank you for including it. I can review it next, but I want to stay on-topic and finish the Mega path first since you asked to fix the bridge.

That said, I already see one important architectural point from your Teensy notes:

  • Teensy should be the reflector tracker
  • Mega should be the motion / waypoint controller
  • ARC should bridge commands between them

That separation is clean and makes sense.


8) My recommendation

Use:

  • MEGA_FIRMWARE_v5 for motion and local decision making
  • MEGA_MGR_v6 as a simple bridge
  • keep step telemetry local unless a trigger happens

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

  1. Revise the Mega firmware one more time so it completely suppresses continuous step telemetry
  2. Review the Teensy tracker files you posted and align their command interface with the new bridge model
  3. Add a tiny ARC skill-side status display using the kebab menu settings so you can monitor only the few variables you actually need

If you’d like, I can do the Mega firmware cleanup next and provide a true MEGA_FIRMWARE_v6 that matches your intended behavior exactly.

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#10  

@Athena. Thank you for reevaluating MEGA_Firmware_v5 and MEGA_MGR_v5.

When I run MEGA_FIRMWARE_v5 with MEGA_MGR _v6, the following happens: (screen shot of Print on MEGA_MGR_v6)

Start
MEGA_MGR_v6 started
TX: WAYPOINT DATA
RX: DESIRED BEARING x100 = 9000
RX: NAV ACK
RX: STATUS
RX: STATUS
RX checksum error. cmd=17 expected=79 got=44
RX checksum error. cmd=17 expected=178 got=155
RX checksum error. cmd=17 expected=108 got=65
RX checksum error. cmd=17 expected=187 got=156
RX checksum error. cmd=17 expected=252 got=233
RX: STATUS
Execution Error Line 201 Col 4 - Not connected
Done (00:00:12.2927604)

As you can see it continues to checksum error, which I believe is leading to the WI-FI disconnect.

I believe you were right stating, "1. Is MEGA_FIRMWARE_v5 sending number of steps as it loops to MEGA_MGR_v5?" I made the following change to MEGA_FIRMWARE_v5:

 totalStepsTaken = (uint32_t)(labs(LeftFrontWheel.currentPosition()) + labs(RightFrontWheel.currentPosition()));
//  reportTotalStepsIfChanged();
}

When MEGA_FIRMWARE_v5 stops calling for 'reportTotalStepsIfChanged()', MEGA_MGR_v6 runs as it should(Screen shot PRINT):

Start
MEGA_MGR_v6 started
TX: WAYPOINT DATA
RX: DESIRED BEARING x100 = 9000
RX: NAV ACK
RX: STATUS
RX: STATUS
RX: STATUS
Done (00:00:18.1078959)

Of course no Step totals were counted and this is not desirable, but by stopping 'if changed' seem to fix the onslaught of STEP data going to MEGA_MGR_v6.

Here's what I am requesting:

  1. A rewrite of MEGA_FIRMWARE_v5 to exclude sending STEPS if changed. Only send STEPS as a follow up to a STOP command being sent or received, so ARC will always be able to know the distance traveled thus far.

As you rewrite MEGA_FIRMWARE, please enable only the following parameters: Let's call the new MEGA_FIRMWARE_v6.

  1. Receive WayPoint Data.
  2. Receive Pivot Data.
  3. Receive STOP command
  4. Receive actual bearing updates.
  5. Send STOP command.
  6. Send Obstacle Detection Scan at required intervals.(step-counts)
  7. Send STEPS whenever a STOP command is Sent or Received.
  8. Send periodic updates of desired bearing, when desired bearing changes by 1 degree.
  9. Count STEPS and STOP when Pivot Data limits are reached or 'combinedStepsDesired' are reached.
  10. Count STEPS for obstacle detection scan intervals and send command.
  11. Control motors based upon differential between desired bearing and actual bearing.
  12. Compute desired bearings if WayPoint Data indicates 'bearing alignment' = 1. (offset reflector position)

2. A rewrite of MEGA_MGR_v6 to match the capabilities listed above of MEGA_FIRMWARE. Let's call the new MEGA_MGR_ v7.

  1. The primary function is a UART bridge to/from ARC.

Thank you very much. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#11  

Absolutely - and yes, the checksum flood you saw is consistent with MEGA_FIRMWARE sending telemetry too frequently for the current bridge design.

What’s happening

The RSP_TOTAL_STEPS packet is still being emitted often enough that the ARC-side manager keeps parsing it and eventually gets out of sync with the stream. That can lead to:

  • repeated checksum errors
  • RX parser desynchronization
  • ARC script execution error / disconnect
  • WiFi instability on the EZ-B side

So your conclusion is correct: the step packets need to be suppressed except on a STOP event.


Your requested behavior

You want:

  • MEGA_FIRMWARE_v6

    • count steps locally
    • do not send steps continuously
    • send steps only when STOP is sent or received
    • still send desired bearing updates when the bearing changes by 1 degree
    • still command obstacle scan at step intervals
    • still control motors from desired vs actual bearing
    • still support waypoint / pivot / stop / actual bearing
  • MEGA_MGR_v7

    • pure UART bridge
    • forward ARC commands to Mega
    • forward Mega packets to ARC
    • not do any control logic itself

That is the right simplification.


Important note about RSP_STATUS and step packets

For MEGA_MGR_v7, I recommend this rule:

  • forward only:
    • READY
    • ACK
    • NAK
    • NAV_ACK
    • STATUS
    • DESIRED_BEARING
    • OBS_SCAN_REQUEST
    • STEP_TOTALS only when STOP occurs

And for the Mega:

  • do not emit step totals in loop()
  • only emit step totals when:
    • STOP command received
    • motors are intentionally stopped
    • leg completes
    • pivot completes, if you want that reported too

MEGA_FIRMWARE_v6

Below is a cleaner rewrite based on your requirements.

#include <AccelStepper.h>

static const uint32_t BAUD = 115200;
static const uint8_t UART_HEADER = 0xA5;

#define LINK_SERIAL Serial3
#define DBG_SERIAL Serial

// ======================================================
// Commands from ARC / MEGA_MGR
// ======================================================
enum CommandId : uint8_t {
  CMD_STOP_MOTORS = 0x01,
  CMD_ACTUAL_BEARING = 0x03,
  CMD_RIGHT_PIVOT = 0x04,
  CMD_LEFT_PIVOT = 0x05,
  CMD_WAYPOINT_DATA = 0x06,
  CMD_RESUME = 0x07,
  CMD_PAUSE = 0x08
};

// ======================================================
// Responses to ARC / MEGA_MGR
// ======================================================
enum ResponseId : uint8_t {
  RSP_NAV_ACK = 0x10,
  RSP_TOTAL_STEPS = 0x11,
  RSP_DESIRED_BEARING = 0x12,
  RSP_OBS_SCAN_REQUEST = 0x13,
  RSP_READY = 0x14,
  RSP_STATUS = 0x15,
  RSP_ACK = 0x16,
  RSP_NAK = 0x17
};

// ======================================================
// Stepper motors
// ======================================================
AccelStepper LeftFrontWheel(AccelStepper::DRIVER, 3, 6);
AccelStepper LeftBackWheel(AccelStepper::DRIVER, 2, 5);
AccelStepper RightFrontWheel(AccelStepper::DRIVER, 12, 13);
AccelStepper RightBackWheel(AccelStepper::DRIVER, 4, 7);

// ======================================================
// Motion state
// ======================================================
enum MotionMode {
  MODE_IDLE = 0,
  MODE_PIVOT = 1,
  MODE_TRACK = 2,
  MODE_PAUSED = 3,
  MODE_COMPLETE = 4
};

MotionMode mode = MODE_IDLE;
MotionMode resumeMode = MODE_IDLE;

bool motorsEnabled = false;
bool systemReady = false;

// ======================================================
// Waypoint / leg data
// ======================================================
uint32_t pathTotalDistance = 0;
uint32_t combinedStepsDesired = 0;
uint32_t oppositeDistance = 0;
uint8_t bearingAlignment = 0;
uint16_t desiredBearing100 = 9000;
uint16_t actualBearing100 = 9000;
uint8_t trackDir = 0;
uint32_t scanIntervalSteps = 3056;

// ======================================================
// Runtime
// ======================================================
uint32_t totalStepsTaken = 0;
uint32_t lastReportedDesiredBearing = 0xFFFF;
uint32_t lastReportedStepsOnStop = 0;

// Leg tracking
uint32_t legStartSteps = 0;
uint32_t nextScanAtSteps = 0;
bool legLoaded = false;
bool scanPending = false;
bool stopJustOccurred = false;

// ======================================================
// Motion control
// ======================================================
int wheelSpeedBase = 1600;
int leftTargetSpeed = 0;
int rightTargetSpeed = 0;
int leftCurrentSpeed = 0;
int rightCurrentSpeed = 0;
int speedRampStep = 25;

// ======================================================
// RX parser
// ======================================================
uint8_t rxState = 0;
uint8_t rxCmd = 0;
uint8_t rxLen = 0;
uint8_t rxIndex = 0;
uint8_t rxChecksum = 0;
uint8_t rxPayload[64];

// ======================================================
// Helpers
// ======================================================
void debugPrint(const String &s) {
  DBG_SERIAL.println(s);
}

void sendBinaryPacket(uint8_t cmd, const uint8_t *payload, uint8_t len) {
  uint8_t cs = cmd ^ len;
  LINK_SERIAL.write(UART_HEADER);
  LINK_SERIAL.write(cmd);
  LINK_SERIAL.write(len);
  for (uint8_t i = 0; i < len; i++) {
    LINK_SERIAL.write(payload[i]);
    cs ^= payload[i];
  }
  LINK_SERIAL.write(cs);
}

void sendAck(const String &msg) {
  uint8_t payload[24];
  uint8_t len = msg.length();
  if (len > 24) len = 24;
  for (uint8_t i = 0; i < len; i++) payload[i] = (uint8_t)msg[i];
  sendBinaryPacket(RSP_ACK, payload, len);
}

void sendNak(const String &msg) {
  uint8_t payload[24];
  uint8_t len = msg.length();
  if (len > 24) len = 24;
  for (uint8_t i = 0; i < len; i++) payload[i] = (uint8_t)msg[i];
  sendBinaryPacket(RSP_NAK, payload, len);
}

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

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

void sendStatus() {
  uint8_t payload[2];
  payload[0] = motorsEnabled ? 1 : 0;
  payload[1] = (uint8_t)mode;
  sendBinaryPacket(RSP_STATUS, payload, 2);
}

void sendNavAck(const char *msg) {
  sendBinaryPacket(RSP_NAV_ACK, (const uint8_t *)msg, strlen(msg));
}

void sendDesiredBearingIfChanged() {
  if (desiredBearing100 != lastReportedDesiredBearing) {
    uint8_t payload[2];
    payload[0] = desiredBearing100 & 0xFF;
    payload[1] = (desiredBearing100 >> 8) & 0xFF;
    sendBinaryPacket(RSP_DESIRED_BEARING, payload, 2);
    lastReportedDesiredBearing = desiredBearing100;
  }
}

void reportStepsOnlyOnStop() {
  uint8_t payload[4];
  payload[0] = totalStepsTaken & 0xFF;
  payload[1] = (totalStepsTaken >> 8) & 0xFF;
  payload[2] = (totalStepsTaken >> 16) & 0xFF;
  payload[3] = (totalStepsTaken >> 24) & 0xFF;
  sendBinaryPacket(RSP_TOTAL_STEPS, payload, 4);
  lastReportedStepsOnStop = totalStepsTaken;
}

void stopMotors() {
  leftTargetSpeed = 0;
  rightTargetSpeed = 0;
  leftCurrentSpeed = 0;
  rightCurrentSpeed = 0;

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

  LeftFrontWheel.runSpeed();
  LeftBackWheel.runSpeed();
  RightFrontWheel.runSpeed();
  RightBackWheel.runSpeed();
}

void applyMotorSpeeds() {
  LeftFrontWheel.setSpeed(leftCurrentSpeed);
  LeftBackWheel.setSpeed(leftCurrentSpeed);
  RightFrontWheel.setSpeed(rightCurrentSpeed);
  RightBackWheel.setSpeed(rightCurrentSpeed);

  LeftFrontWheel.runSpeed();
  LeftBackWheel.runSpeed();
  RightFrontWheel.runSpeed();
  RightBackWheel.runSpeed();
}

void rampTowardTargets() {
  if (leftCurrentSpeed < leftTargetSpeed) {
    leftCurrentSpeed += speedRampStep;
    if (leftCurrentSpeed > leftTargetSpeed) leftCurrentSpeed = leftTargetSpeed;
  } else if (leftCurrentSpeed > leftTargetSpeed) {
    leftCurrentSpeed -= speedRampStep;
    if (leftCurrentSpeed < leftTargetSpeed) leftCurrentSpeed = leftTargetSpeed;
  }

  if (rightCurrentSpeed < rightTargetSpeed) {
    rightCurrentSpeed += speedRampStep;
    if (rightCurrentSpeed > rightTargetSpeed) rightCurrentSpeed = rightTargetSpeed;
  } else if (rightCurrentSpeed > rightTargetSpeed) {
    rightCurrentSpeed -= speedRampStep;
    if (rightCurrentSpeed < rightTargetSpeed) rightCurrentSpeed = rightTargetSpeed;
  }
}

void setMotionMode(MotionMode newMode) {
  if (mode == newMode) return;
  mode = newMode;
  sendStatus();
}

void startLeg() {
  legStartSteps = totalStepsTaken;
  nextScanAtSteps = scanIntervalSteps;
  legLoaded = true;
  scanPending = false;
  stopJustOccurred = false;
  motorsEnabled = true;
}

void requestObstacleScan() {
  sendBinaryPacket(RSP_OBS_SCAN_REQUEST, nullptr, 0);
}

void handleStopEvent(const char *reason) {
  debugPrint(String("STOP: ") + reason);
  stopMotors();
  motorsEnabled = false;
  setMotionMode(MODE_IDLE);
  reportStepsOnlyOnStop();
  sendAck("STOP");
}

void handleWaypointPacket(const uint8_t *p, uint8_t len) {
  if (len != 22) {
    sendNak("WAYPOINT_LEN");
    return;
  }

  pathTotalDistance = readU32LE(&p[0]);
  combinedStepsDesired = readU32LE(&p[4]);
  oppositeDistance = readU32LE(&p[8]);
  bearingAlignment = p[12];
  desiredBearing100 = readU16LE(&p[13]);
  actualBearing100 = readU16LE(&p[15]);
  trackDir = p[17];
  scanIntervalSteps = readU32LE(&p[18]);

  debugPrint("WAYPOINT received");
  debugPrint("pathTotalDistance=" + String(pathTotalDistance));
  debugPrint("combinedStepsDesired=" + String(combinedStepsDesired));
  debugPrint("oppositeDistance=" + String(oppositeDistance));
  debugPrint("bearingAlignment=" + String(bearingAlignment));
  debugPrint("desiredBearing100=" + String(desiredBearing100));
  debugPrint("actualBearing100=" + String(actualBearing100));
  debugPrint("trackDir=" + String(trackDir));
  debugPrint("scanIntervalSteps=" + String(scanIntervalSteps));

  if (bearingAlignment == 1) {
    // Offset reflector position logic can be refined later
    // For now this just acknowledges that desired bearing can be adjusted locally.
  }

  sendDesiredBearingIfChanged();
  sendNavAck("WAYPOINT");
  setMotionMode(MODE_PIVOT);
  startLeg();
}

void handleBinaryPacket(uint8_t cmd, const uint8_t *payload, uint8_t len) {
  switch (cmd) {
    case CMD_STOP_MOTORS:
      handleStopEvent("CMD_STOP_MOTORS");
      break;

    case CMD_ACTUAL_BEARING:
      if (len != 2) {
        sendNak("ACTUAL_BEARING_LEN");
        return;
      }
      actualBearing100 = readU16LE(payload);
      sendAck("ACTUAL_BEARING");
      break;

    case CMD_RIGHT_PIVOT:
      if (len != 2) {
        sendNak("RIGHT_PIVOT_LEN");
        return;
      }
      combinedStepsDesired = readU16LE(payload);
      motorsEnabled = true;
      setMotionMode(MODE_PIVOT);
      sendAck("RIGHT_PIVOT");
      break;

    case CMD_LEFT_PIVOT:
      if (len != 2) {
        sendNak("LEFT_PIVOT_LEN");
        return;
      }
      combinedStepsDesired = readU16LE(payload);
      motorsEnabled = true;
      setMotionMode(MODE_PIVOT);
      sendAck("LEFT_PIVOT");
      break;

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

    case CMD_RESUME:
      motorsEnabled = true;
      setMotionMode(resumeMode);
      sendAck("RESUME");
      break;

    case CMD_PAUSE:
      resumeMode = mode;
      motorsEnabled = false;
      setMotionMode(MODE_PAUSED);
      stopMotors();
      sendAck("PAUSE");
      break;

    default:
      sendNak("UNKNOWN_CMD");
      break;
  }
}

void processIncoming() {
  while (LINK_SERIAL.available() > 0) {
    uint8_t b = (uint8_t)LINK_SERIAL.read();

    switch (rxState) {
      case 0:
        if (b == UART_HEADER) rxState = 1;
        break;

      case 1:
        rxCmd = b;
        rxChecksum = rxCmd;
        rxState = 2;
        break;

      case 2:
        rxLen = b;
        rxChecksum ^= rxLen;
        rxIndex = 0;
        if (rxLen > sizeof(rxPayload)) {
          rxState = 0;
          sendNak("LEN_OVF");
        } else {
          rxState = (rxLen == 0) ? 4 : 3;
        }
        break;

      case 3:
        rxPayload[rxIndex++] = b;
        rxChecksum ^= b;
        if (rxIndex >= rxLen) rxState = 4;
        break;

      case 4:
        if (b == rxChecksum) {
          handleBinaryPacket(rxCmd, rxPayload, rxLen);
        } else {
          sendNak("CS");
        }
        rxState = 0;
        rxCmd = 0;
        rxLen = 0;
        rxIndex = 0;
        rxChecksum = 0;
        break;
    }
  }
}

void updateMotion() {
  if (!motorsEnabled) {
    stopMotors();
    return;
  }

  uint32_t stepsInLeg = totalStepsTaken - legStartSteps;

  if (mode == MODE_PIVOT) {
    int error = (int)desiredBearing100 - (int)actualBearing100;

    if (error > 25) {
      leftTargetSpeed = wheelSpeedBase;
      rightTargetSpeed = -wheelSpeedBase;
    } else if (error < -25) {
      leftTargetSpeed = -wheelSpeedBase;
      rightTargetSpeed = wheelSpeedBase;
    } else {
      // Pivot complete -> move into track mode
      leftTargetSpeed = 0;
      rightTargetSpeed = 0;
      setMotionMode(MODE_TRACK);
    }
  }

  if (mode == MODE_TRACK) {
    int error = (int)desiredBearing100 - (int)actualBearing100;
    int steer = error / 50;

    if (steer > 400) steer = 400;
    if (steer < -400) steer = -400;

    leftTargetSpeed = wheelSpeedBase + steer;
    rightTargetSpeed = wheelSpeedBase - steer;

    if (leftTargetSpeed > wheelSpeedBase + 400) leftTargetSpeed = wheelSpeedBase + 400;
    if (rightTargetSpeed > wheelSpeedBase + 400) rightTargetSpeed = wheelSpeedBase + 400;
    if (leftTargetSpeed < -wheelSpeedBase) leftTargetSpeed = -wheelSpeedBase;
    if (rightTargetSpeed < -wheelSpeedBase) rightTargetSpeed = -wheelSpeedBase;

    if (!scanPending && scanIntervalSteps > 0 && stepsInLeg >= nextScanAtSteps) {
      scanPending = true;
      requestObstacleScan();
      nextScanAtSteps += scanIntervalSteps;
    }

    if (combinedStepsDesired > 0 && stepsInLeg >= combinedStepsDesired) {
      handleStopEvent("LEG_COMPLETE");
      return;
    }
  }

  if (mode == MODE_PAUSED || mode == MODE_COMPLETE || mode == MODE_IDLE) {
    leftTargetSpeed = 0;
    rightTargetSpeed = 0;
  }

  rampTowardTargets();
  applyMotorSpeeds();

  totalStepsTaken = (uint32_t)(labs(LeftFrontWheel.currentPosition()) + labs(RightFrontWheel.currentPosition()));
}

void setup() {
  DBG_SERIAL.begin(BAUD);
  LINK_SERIAL.begin(BAUD);

  LeftFrontWheel.setMaxSpeed(wheelSpeedBase * 2);
  LeftBackWheel.setMaxSpeed(wheelSpeedBase * 2);
  RightFrontWheel.setMaxSpeed(wheelSpeedBase * 2);
  RightBackWheel.setMaxSpeed(wheelSpeedBase * 2);

  debugPrint("MEGA_FIRMWARE_v6 started");

  uint8_t payload[] = { 'R', 'E', 'A', 'D', 'Y' };
  sendBinaryPacket(RSP_READY, payload, 5);
  sendStatus();
}

void loop() {
  processIncoming();

  // Only update bearing announcement when it changes by 1 degree or more.
  sendDesiredBearingIfChanged();

  updateMotion();

  delay(1);
}

MEGA_MGR_v7

This one is a bridge only. It forwards commands and packets, and does not try to interpret step totals or control the robot.

var UART_INDEX = 0;
var UART_BAUD = 115200;
var UART_HEADER = 0xA5;

// Commands from ARC
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;
var CMD_RESUME = 0x07;
var CMD_PAUSE = 0x08;

// Responses from Mega
var RSP_NAV_ACK = 0x10;
var RSP_TOTAL_STEPS = 0x11;
var RSP_DESIRED_BEARING = 0x12;
var RSP_OBS_SCAN_REQUEST = 0x13;
var RSP_READY = 0x14;
var RSP_STATUS = 0x15;
var RSP_ACK = 0x16;
var RSP_NAK = 0x17;

var rxState = 0;
var rxCmd = 0;
var rxLen = 0;
var rxIndex = 0;
var rxPayload = [];
var rxChecksum = 0;

function safeInt(v, def) {
  var n = parseInt(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 bytesToU16LE(b0, b1) {
  return (b0 & 0xFF) | ((b1 & 0xFF) << 8);
}

function sendPacket(cmd, payload) {
  var checksum = cmd ^ payload.length;
  UART.hardwareUartWrite(UART_INDEX, [UART_HEADER]);
  UART.hardwareUartWrite(UART_INDEX, [cmd]);
  UART.hardwareUartWrite(UART_INDEX, [payload.length]);
  if (payload.length > 0) {
    UART.hardwareUartWrite(UART_INDEX, payload);
    for (var i = 0; i < payload.length; i++) {
      checksum ^= (payload[i] & 0xFF);
    }
  }
  UART.hardwareUartWrite(UART_INDEX, [checksum & 0xFF]);
}

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

function sendActualBearing() {
  var bearing100 = safeInt(getVar("$actualBearing100", 9000), 9000);
  bearing100 = Math.max(0, Math.min(18000, bearing100));
  sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
  print("TX: ACTUAL BEARING = " + bearing100);
}

function sendWaypointData() {
  var pathTotalDistance = safeInt(getVar("$pathTotalDistance", 0), 0) >>> 0;
  var combinedStepsDesired = safeInt(getVar("$combinedStepsDesired", 0), 0) >>> 0;
  var oppositeDistance = safeInt(getVar("$oppositeDistance", 0), 0) >>> 0;
  var bearingAlignment = safeInt(getVar("$bearingAlignment", 0), 0) & 0xFF;
  var trackDir = safeInt(getVar("$trackDir", 0), 0) ? 1 : 0;
  var desiredBearing100 = safeInt(getVar("$desiredBearing100", 9000), 9000);
  var actualBearing100 = safeInt(getVar("$actualBearing100", 9000), 9000);
  var scanIntervalSteps = safeInt(getVar("$scanIntervalSteps", 3056), 3056) >>> 0;

  var payload = [];
  payload = payload.concat(u32ToBytes(pathTotalDistance));
  payload = payload.concat(u32ToBytes(combinedStepsDesired));
  payload = payload.concat(u32ToBytes(oppositeDistance));
  payload.push(bearingAlignment);
  payload = payload.concat(u16ToBytes(desiredBearing100));
  payload = payload.concat(u16ToBytes(actualBearing100));
  payload.push(trackDir);
  payload = payload.concat(u32ToBytes(scanIntervalSteps));

  if (payload.length !== 22) {
    print("Waypoint payload length error: " + payload.length);
    return;
  }

  setVar("$legComplete", 0);
  setVar("$navAckReceived", 0);

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

function sendPivot(cmd, name) {
  var steps = safeInt(getVar("$pivotSteps", 0), 0) >>> 0;
  sendPacket(cmd, u16ToBytes(steps));
  print("TX: " + name + " steps=" + steps);
}

function sendPause() {
  sendPacket(CMD_PAUSE, []);
  print("TX: PAUSE");
}

function sendResume() {
  sendPacket(CMD_RESUME, []);
  print("TX: RESUME");
}

function handleRxPacket(cmd, payload) {
  if (cmd == RSP_READY) {
    print("RX: READY");
    setVar("$megaReady", 1);
    return;
  }

  if (cmd == RSP_ACK) {
    print("RX: ACK");
    return;
  }

  if (cmd == RSP_NAK) {
    print("RX: NAK");
    return;
  }

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

  if (cmd == RSP_STATUS) {
    print("RX: STATUS");
    return;
  }

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

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

  // Ignore step totals unless you explicitly want to display them later.
}

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

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

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

    if (rxState == 1) {
      rxCmd = b;
      rxChecksum = rxCmd;
      rxState = 2;
      continue;
    }

    if (rxState == 2) {
      rxLen = b;
      rxChecksum ^= rxLen;
      rxPayload = [];
      rxIndex = 0;
      rxState = (rxLen == 0) ? 4 : 3;
      continue;
    }

    if (rxState == 3) {
      rxPayload.push(b);
      rxChecksum ^= b;
      rxIndex++;
      if (rxIndex >= rxLen) rxState = 4;
      continue;
    }

    if (rxState == 4) {
      var cs = b & 0xFF;
      if (cs == (rxChecksum & 0xFF)) {
        handleRxPacket(rxCmd, rxPayload);
      } else {
        print("RX checksum error. cmd=" + rxCmd + " expected=" + (rxChecksum & 0xFF) + " got=" + cs);
      }

      rxState = 0;
      rxCmd = 0;
      rxLen = 0;
      rxIndex = 0;
      rxPayload = [];
      rxChecksum = 0;
      continue;
    }
  }
}

print("MEGA_MGR_v7 started");
UART.initHardwareUart(UART_INDEX, UART_BAUD);

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

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

  if (getVar("$sendPauseNow", 0) == 1) {
    sendPause();
    setVar("$sendPauseNow", 0);
  }

  if (getVar("$sendResumeNow", 0) == 1) {
    sendResume();
    setVar("$sendResumeNow", 0);
  }

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

  if (getVar("$sendRightPivotNow", 0) == 1) {
    sendPivot(CMD_RIGHT_PIVOT, "RIGHT PIVOT");
    setVar("$sendRightPivotNow", 0);
  }

  if (getVar("$sendLeftPivotNow", 0) == 1) {
    sendPivot(CMD_LEFT_PIVOT, "LEFT PIVOT");
    setVar("$sendLeftPivotNow", 0);
  }

  processIncomingBytes();
  sleep(20);
}

What this changes

Mega

  • no continuous step telemetry
  • steps are only reported on STOP or leg-complete stop
  • obstacle scan requests are sent only at intervals
  • desired bearing is only reported when changed

Manager

  • bridge only
  • no step display spam
  • no control logic
  • no parser flood from step packets

About your Teensy files

Yes, I can review them, but I do not have any hidden or private "latest" files beyond what you pasted here. If you want, I can now help align the Teensy protocol to this bridge model.

If you want to continue, I recommend the next step be:

  1. test MEGA_FIRMWARE_v6 with MEGA_MGR_v7
  2. confirm stop reports steps only once
  3. then we move to the Teensy side

If you’d like, I can also make a MEGA_FIRMWARE_v6.1 that adds a tiny LEG_COMPLETE response so ARC can distinguish "stop because commanded" vs "stop because leg ended."

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
📚 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.