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

Yes - there are two likely root causes here:
I’ll break this down clearly.
1) UART mapping verification
You noted:
Serial3:That is only correct if the ARC-side hardware UART index 0 is actually wired to the Mega’s Serial3 pins.
On the Arduino Mega 2560
The hardware UART pins are:
Serial= USB / UART0Serial1= pins 18 (TX1) / 19 (RX1)Serial2= pins 16 (TX2) / 17 (RX2)Serial3= pins 14 (TX3) / 15 (RX3)In ARC
UART.hardwareUartInit(uartIndex, baud)refers to the EZ-B’s own hardware UART index, not the Mega’sSerialnumbers.So the mapping is:
If your ARC EZ-B hardware UART #0 is physically wired to the Mega’s
Serial3, thenUART_INDEX = 0is correct.Important physical wiring note
You must cross-connect:
And the Mega
Serial3pins are:So if you’re using
Serial3, the Mega should be connected to the EZ-B UART port that corresponds to your ARC hardware UART index 0.If you want, I can help you map the exact ARC UART connector pins once you tell me which EZ-B board / UART header you’re using.
2) Why the current Mega firmware likely breaks after the first packet
Your pasted
Mega_Firmware_v3contains multiple issues that stand out immediately:Major problems in the sketch you posted
#includeis incompletebytesToU16LE()returns0readU16LE()appears malformed: That should be<< 8, not= 1lastReportedDegis referenced but not declaredupdateMotionControl()is referenced but not includedprintLegInfoOnce(),printStopOnce(),printActualBearingOnce()are referenced but not includedsendBinaryPacket()is referenced but not includedSo the pasted version is not just a little off - it looks like an incomplete or corrupted merge.
That explains why:
3) Protocol verification
Your packet format is:
0xA5That part is consistent between the two snippets.
Good news
The ARC manager script and Mega parser both use XOR across:
cmdlenSo the checksum logic itself is aligned.
But there is one more thing to verify
The ARC manager script sends packets only over binary UART, while your Mega sketch also tries to parse text lines from the same serial port:
processIncoming()processTextSerial()This is dangerous because:
For a binary protocol, I strongly recommend:
4) Recommended fix: rewrite the Mega firmware cleanly
Below is a cleaner Mega_Firmware_v4 that:
Serial3for ARC linkMega_Firmware_v4.cpp5) Revised ARC manager script
Your manager script is close, but I’d recommend cleaning it up so it only does binary protocol, with no placeholder text handshake expectations.
Here is a revised Mega_Mgr_v4:
6) Why the "first packet only" symptom happens
That symptom usually points to one of these:
In your case, I’d bet the biggest issues are:
7) What I’d do next
On the Mega
Use only:
Serial3for ARC communicationsSerialfor debug text onlyIn ARC
Confirm the UART index you are using matches the physical UART port wired to the Mega
Serial3.Test sequence
Mega_Firmware_v4Waypoint receivedACK8) A question that will help me finish this for you
Can you confirm which physical EZ-B UART pins are wired to the Mega?
For example:
If you want, I can also help you convert the motor section into a cleaner waypoint-following state machine once the UART link is solid.
@Athena. Thank you for
Mega_Firmware_v4.cpp andMega_Mgr_v4. They work solid after multiple tests. To confirm the UART connections between the EXB and Mega:Can you convert the motor section into a cleaner waypoint - following state machine now that the UART link is solid? As you continue with Mega_Firmware_v4.ccp, can you consider the following recommendations?
Serial3= ARC/EZB linkSerial= USB debug monitorwhileloops that trap the processorgotoloop()Thank you. Jack
Absolutely - and thanks for confirming the wiring. That mapping is correct:
That means your ARC
UART_INDEX = 0setup is correct for this wiring.What I’ll change in
Mega_Firmware_v4I’ll keep the comms layer intact and convert the motion logic into a cleaner, non-blocking waypoint-following state machine with:
Serial3reserved for the ARC/EZB linkSerialreserved for USB debugloop()whileloops that trap executiongotoloop()Recommended approach
The cleanest structure is:
loop()This keeps UART responsive and avoids the "firmware feels stuck" problem.
Revised firmware:
Mega_Firmware_v4.cppBelow is a cleaner version of the motion section while preserving your packet protocol and UART behavior.
Notes on the state machine
This version makes the motion logic much easier to extend later. Right now it does:
You can later add more phases, such as:
A few things to verify with your actual drive train
Because stepper setups vary a lot, I want to confirm one thing:
Do all four motors rotate the same physical direction when given the same positive speed?
If not, one or more motors may need:
Right now I used a simple assumption that:
If one side is mechanically reversed, I can help you add a small per-motor invert table so the state machine remains clean.
One more improvement I recommend
Your protocol currently uses:
That is fine, but for waypoint motion it helps a lot to also send:
RSP_STATUSRSP_NAV_ACKon key state transitions, especially if ARC needs to know when a leg starts or ends.
Next question for you
Before I tune the waypoint behavior further, can you tell me:
combinedStepsDesiredrepresent total distance traveled or just the current leg?If you answer those, I can refine
Mega_Firmware_v4into a more complete waypoint controller rather than just a motion state framework.@Athena. Thank you for the Revised MEGA_FIRMWARE_v4. The revised MEGA_FIRMWARE_v4 only allows the motors to run after initial upload . Repeated attempts from MEGA_MGR_v4 (that runs in a robot skill on ARC) to activate the Mega board only debugs the Waypoint Data, but motors do not run. Only after a fresh upload of the MEGA_FIRMWARE_v4 do the motors run when the Mega board is activated from MEGA_MGR_v4.
Can you troubleshoot MEGA_FIRMWARE_v4 and rewrite? Let's call it MEGA_FIRMWARE_v5.
To answer some of your previous questions:
All motors run at the same speed, fwd rev left pivot right pivot.
Your protocol currently uses:
That is fine, but for waypoint motion it helps a lot to also send:
RSP_STATUSRSP_NAV_ACKWhat do you mean by RSP_STATUS AND RSP_NAV_ACK? How will this fit into the interface of Mega and ARC?
combinedStepsDesiredrepresent total distance traveled and is only for that LEG. When the LEG is completed, A new LEG WayPoint Data will be sent.All LEGs, before tracking commences, will Pivot to align the rover's actual bearing to the leg's desired bearing. CombinedStepsDesired will be zeroed and tracking commenced.
All obstacle scans when called should automatically pause tracking, after obstacle scan, a STOP command or RESUME command will be issued. The obstacle scan intervals are based upon steps accrued and these scans are above and beyond the obstacle scan performed during Left and Right servo sweep reflector tracking. (posObsTot and negObsTot)
I hope the above info helps. My number one goal for MEGA_FIRMWARE is simplicity, with orderly logic. Making it more complex, while theoretically better, is best left to later revisions far down the road when all the current pieces are reliably working together. (MEGA_FIRMWARE / MEGA_MGR / TEENSY_FIRMWARE / TEENSY_MGR / ARC robot skills: Command, Monitor, Leg, Pivots.
I will be testing the latest MEGA_FIRMWARE with the most Recent MEGA_MGR_v4. Please refer to your last post for latest MEGA_MGR_v4 if you think it should be adjusted with the MEGA_FIRMWARE rewrite. After successful integration of MEGA_FIRMWARE with MEGA_MGR, I would like you to review latest TEENSY_FIRMWARE and TEENSY_MGR.
Thank you. Jack
Absolutely - and your symptom description is very helpful.
What you’re seeing strongly suggests the firmware is entering a "works after reset/upload, then never re-arms correctly" state, which usually happens when:
Given your goal of simplicity and orderly logic, I agree: we should simplify rather than add complexity.
What I mean by
RSP_STATUSandRSP_NAV_ACKThese are optional Mega ARC status packets that help ARC know what the Mega is doing.
RSP_NAV_ACKThis is a short acknowledgment that says:
It is useful when ARC needs confirmation that a leg was accepted before sending the next instruction.
RSP_STATUSThis is a lightweight status report like:
Example meaning:
STATUS,TRACKING,ENABLED,STEPS=1234STATUS,PIVOTING,ENABLEDSTATUS,PAUSED,WAITING_FOR_RESUMEHow it fits into ARC / Mega interface
You do not need to build a complicated interface for it.
ARC already has two clean options:
So for example:
RSP_NAV_ACKwhen a waypoint is accepted$navAckReceived = 1RSP_STATUSwhenever the mode changes$megaMode = "TRACKING"$megaMotorEnabled = 1That’s all it is - just better feedback between the two sides.
Since you want simplicity, I’d suggest:
RSP_NAV_ACKRSP_STATUSWhy your current MEGA_FIRMWARE_v4 likely behaves this way
The most likely cause is that the motion state is not being re-armed cleanly after the first upload/start sequence.
In other words:
That points to a design issue like:
motorsEnabledis not set in the right placemodeorphaseis not transitioned correctly after stop/completeWaypoint Dataupdates numbers but doesn’t explicitly start the next legSTOPorRESUMEbehavior is not handled as an actual state transitionRecommended design for
MEGA_FIRMWARE_v5For simplicity, I’d use a very small state model:
Motion states
IDLEPIVOTTRACKPAUSEDCOMPLETELeg sequence
WAYPOINT DATAphase = PIVOTphase = TRACKphase = COMPLETEObstacle scan behavior
When scan is requested:
phase = PAUSEDRESUMEThat keeps the logic simple and prevents motion from being trapped by a command parser.
MEGA_FIRMWARE_v5.cppBelow is a cleaner rewrite that keeps your protocol, keeps
Serial3for ARC, usesSerialonly for debug, avoids blocking loops, and makes the motion lifecycle explicit.What changed from v4
1. Clear leg lifecycle
WAYPOINT DATAnow explicitly starts a legstepsAtLegStart2. Clean state transitions
MODE_PIVOTMODE_TRACKMODE_PAUSEDMODE_COMPLETE3. Simple resume/pause behavior
CMD_PAUSEstores the current modeCMD_RESUMErestores it4. Debug prints only on changes
I kept debug output focused on:
5. Motors update continuously in
loop()No blocking loops were introduced.
Do I recommend changes to
MEGA_MGR_v4?Only small ones.
If you adopt
CMD_PAUSEandCMD_RESUME, then ARC can explicitly manage obstacle scans like this:PAUSERESUMEIf you want to keep things even simpler, you can skip those commands for now and just use
STOPfollowed by a newWAYPOINT DATAor new resume logic later.One small ARC-side recommendation
When
WAYPOINT DATAis sent, the manager should also clear any old flags like:$legComplete$navAckReceivedThat keeps the ARC project logic clean.
On your obstacle scan workflow
Your statement makes sense:
This firmware now supports that philosophy more cleanly.
Important note about the pivot behavior
Right now, pivot completion depends on
actualBearing100being updated from ARC.So ARC needs to keep sending updated actual bearing while pivoting. That seems consistent with your current architecture.
If you want, I can next revise the ARC manager so it automatically:
RSP_STATUSNext step
If you’d like, I can do one of these next:
MEGA_MGR_v4to fully matchMEGA_FIRMWARE_v5If you want the cleanest path, I’d do MEGA_MGR_v4 next so both ends are perfectly aligned before moving to Teensy.
@Athena. Thank you. MEGA_FIRMWARE_v5 didn't compile at first, but I found these two inconsistencies:
I changed 'motorEnabled' in the second statement to 'motorsEnabled' and the sketch compiled and ran. If this was the correct thing to do, perhaps you could correct v5 on your end.
I am getting the expected operation (motors run) and stop as expected without the need to keep uploading the sketch. What is happening is the EZB WI-FI disconnects after the 2nd cycle of sending Waypoint Data to MEGA_MGR_v4. This WI-FI disconnect happens consistently on the 2nd cycle. I think it may be time to reexamine MEGA_MGR_v4. Your concerns from your last Post:
Do I recommend changes to
MEGA_MGR_v4?PAUSERESUMEAnswer: Please do these but understand the MEGA_FIRMWARE is counting steps and issues a CMD to MEGA_MGR to (1) Perform Obstacle Scan. MEGA_FIRMWARE continues counting steps, computing desired bearing and controlling motors based upon the last actual bearing that it had. MEGA_FIRMWARE simply advises MEGA_MGR to (1) Perform Obstacle Scan. On this CMD, MEGA_MGR (1) stops tracking the reflector and runs an obstacle scan function and either issues a STOP to MEGA_FIRMWARE or resumes tracking after obstacle scan and starts producing actual bearing updates. Since MEGA_FIRMWARE has never stopped doing any of its functions during the obstacle scan it simply takes new actual bearing updates. When the next obstacle scan is triggered by step accrual, MEGA_FIRMWARE sends another (1) Perform Obstacle Scan.
One small ARC-side recommendation
When
WAYPOINT DATAis sent, the manager should also clear any old flags like:$legComplete$navAckReceivedAnswer: Please do these.
On your obstacle scan workflow
Your statement makes sense:
This firmware now supports that philosophy more cleanly.
Answer: Exactly what I want, except for the last bullet point, "after scan.ARC sends STOP or No Other Commands. If a STOP is not generated, ARC (MEGA_MGR) simply resumes sending actual bearing updates.
Important note about the pivot behavior
Right now, pivot completion depends on
actualBearing100being updated from ARC. So ARC needs to keep sending updated actual bearing while pivoting. That seems consistent with your current architecture. If you want, I can next revise the ARC manager so it automatically:RSP_STATUSAnswer: The above seems way too complicated. A spinning rover has a very difficult time tracking a reflector. Pivot architecture is still fluid. I envision it based upon steps computed for the pivot required.
Next step
If you’d like, I can do one of these next:
MEGA_MGR_v4to fully matchMEGA_FIRMWARE_v5If you want the cleanest path, I’d do MEGA_MGR_v4 next so both ends are perfectly aligned before moving to Teensy.
Answer:
Thanks for all you do. Jack
Absolutely - and yes, your correction was the right one.
1) Small correction to
MEGA_FIRMWARE_v5You were correct to change:
motorEnabledmotorsEnabledThat was just an inconsistency in my draft. If the sketch is compiling and running now, your fix was the correct one.
2) Why the EZB Wi-Fi disconnects on the 2nd cycle
Given your description, this is very likely not a UART framing problem anymore. Since the first cycle works and the second cycle reliably triggers a Wi-Fi disconnect, the most likely causes are:
MEGA_MGR_v4Since you want simplicity, I agree we should simplify
MEGA_MGR_v4and keep the behavior explicit.3) Your pivot architecture makes sense
Your 5-step pivot flow is much cleaner:
That is a far better design than trying to have Mega "track reflector bearing" while spinning.
So for MEGA_MGR_v5, I’ll keep the logic simple:
STOPif a full stop is neededThat matches your intended behavior much better.
4) What a "status monitor" means
This was just an optional idea, not something required.
A status monitor in ARC would simply be a small script or text display that shows live values like:
Example:
MODE = TRACKSTEPS = 1832ACK = 1It’s useful for debugging, but if you feel it adds complexity, we can skip it for now.
5) About the latest Teensy files
I do not have your local latest
TEENSY_v4files unless you paste them here.So for the Teensy side, I can only review what you share in the thread. If you post:
.inoPacketHandler.hPacketHandler.cppI can review them and help align them with the current MEGA protocol.
MEGA_MGR_v5
Below is a cleaned-up version of the ARC manager script that:
MEGA_FIRMWARE_v5RSP_STATUSandRSP_NAV_ACKPAUSE/RESUME6) How
RSP_STATUScan be used without making things complexSince you prefer simplicity, I’d use
RSP_STATUSonly as a lightweight debug/status indicator.For example, ARC could store:
$megaMotorEnabled$megaModeThen your ARC logic can decide whether to send the next waypoint or pivot command.
You do not need a full monitor UI unless you want one later.
7) My recommendation for the next test
Please test in this order:
MEGA_FIRMWARE_v5MEGA_MGR_v5RSP_NAV_ACKis receivedRSP_STATUSis receivedIf the Wi-Fi disconnect still happens on cycle 2, the next thing I’d inspect is:
If you want, I can next do one of these:
MEGA_FIRMWARE_v5further to formally add a tiny 2-byteRSP_STATUSpayloadMEGA_MGR_v5to support your pivot-step workflow more directlyAnd yes - if you paste the latest Teensy
.ino/.h/.cpp, I can work from those exactly.@Athena. Thank you for MEGA_MGR_v5. I tested it in the prescribed order:
MEGA_FIRMWARE_v5MEGA_MGR_v5RSP_NAV_ACKis receivedRSP_STATUSis receivedNo WI-FI disconnect after multiple cycles of WayPoint Data. So, it seems MEGA_FIRMWARE_v5 and MEGA_MGR_v5 are working together.
However, it seems MEG_MGR_v5 is sending a STOP command unexpectedly. I copied the skill print below:
I had to comment out // :
This was counting steps and filling up the print space.
Questions:
If MEGA_FIRMWARE is sending steps to MEGA_MGR, this is not what I intended. Almost all data within MEGA_FIRMWARE stays in MEGA_FIRMWARE until a 'trigger' is hit.
MY GOALS for MEGA_FIRMWARE_v5:
Notice that MEGA_FIRMWARE keeps all step counting locally, until something is triggered (obstacle scan, leg completion, etc.). Desired bearing calculation, differential calculations are all kept locally, except for periodic desired bearing updates to ARC.
MY GOALS FOR MEGA_MGR_v5:
Notice that MEGA_MGR does not compute or monitor anything, it just provides a UART bridge from MEGA_FIRMWARE to ARC.
So, based upon the GOALS for MEGA_FIRMWARE_v5, and the GOALS for MEGA_MGR_v5, can you please reexamine them for compliance and rewrite as necessary?
Attached are Teensy (IR_TRACKERv4.ino.h.cpp and PacketHandler.h.cpp) for your consideration.
Thank you. Jack
TEENSY: