@Athena - can you review the Arduino sketch below and explain why the left motors (LeftFront and LeftBack) do not run until sendObstacleScanRequest() has been called?
Context: the sketch communicates with ARC over Serial3 using a simple UART protocol (header 0xA5), so the Arduino is acting as the EZB / hardware controller receiving navigation/waypoint commands from ARC (for example, commands sent by a Robot Skill). The four motors are driven as steppers using AccelStepper and MultiStepper. I’ve included the full sketch below.
Thanks, Jack
#include <AccelStepper.h>
#include <MultiStepper.h>
static const uint8_t UART_HEADER = 0xA5;
// ARC -> Arduino
static const uint8_t CMD_STOP_MOTORS = 0x01;
static const uint8_t CMD_ACTUAL_BEARING = 0x03;
static const uint8_t CMD_RIGHT_PIVOT = 0x04;
static const uint8_t CMD_LEFT_PIVOT = 0x05;
static const uint8_t CMD_WAYPOINT_DATA = 0x06;
// Arduino -> ARC
static const uint8_t RSP_NAV_ACK = 0x10;
static const uint8_t RSP_TOTAL_STEPS = 0x11;
static const uint8_t RSP_DESIRED_BEARING = 0x12;
static const uint8_t RSP_OBS_SCAN_REQUEST = 0x13;
AccelStepper LeftFrontWheel(AccelStepper::DRIVER, 3, 6);
AccelStepper LeftBackWheel(AccelStepper::DRIVER, 2, 5);
AccelStepper RightFrontWheel(AccelStepper::DRIVER, 12, 13);
AccelStepper RightBackWheel(AccelStepper::DRIVER, 4, 7);
MultiStepper multiStepper;
enum ParserState { WAIT_HEADER, WAIT_CMD, WAIT_PAYLOAD };
ParserState state = WAIT_HEADER;
uint8_t currentCmd = 0;
uint8_t payload[32];
uint8_t payloadIndex = 0;
uint8_t expectedLength = 0;
int RUN = 0;
uint32_t totalStepsTaken = 0;
uint32_t combinedStepsDesired = 0;
uint32_t pathTotalDistance = 0;
uint32_t oppositeDistance = 0;
uint8_t bearingAlignment = 0;
uint16_t reflectorBearing100 = 0;
uint8_t sensorPrime = 0;
uint16_t waypointDesiredBearing100 = 9000;
uint16_t waypointActualBearing100 = 9000;
uint8_t trackDir = 0;
uint16_t actualBearing100 = 9000;
uint16_t desiredBearing100 = 9000;
uint16_t lastSentDesiredBearing100 = 0xFFFF;
int16_t bearingDiffTarget = 0;
int16_t bearingDiffRamped = 0;
bool obstacleScanRequested = false;
bool scanNeutralMode = false;
uint32_t nextScanStep = 10500;
const uint32_t scanIntervalSteps = 10500;
uint16_t currentSpeed = 0;
uint16_t normalSpeed = 800;
uint16_t readUInt16LE(const uint8_t* data) {
return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}
uint32_t readUInt32LE(const uint8_t* data) {
return (uint32_t)data[0]
| ((uint32_t)data[1] << 8)
| ((uint32_t)data[2] << 16)
| ((uint32_t)data[3] << 24);
}
void writeUInt16LE(uint16_t value) {
Serial3.write((uint8_t)(value & 0xFF));
Serial3.write((uint8_t)((value >> 8) & 0xFF));
}
void writeUInt32LE(uint32_t value) {
Serial3.write((uint8_t)(value & 0xFF));
Serial3.write((uint8_t)((value >> 8) & 0xFF));
Serial3.write((uint8_t)((value >> 16) & 0xFF));
Serial3.write((uint8_t)((value >> 24) & 0xFF));
}
void sendHeaderAndCmd(uint8_t cmd) {
Serial3.write(UART_HEADER);
Serial3.write(cmd);
}
void sendNavAck() {
sendHeaderAndCmd(RSP_NAV_ACK);
Serial.println("TX: NAV ACK");
}
void sendTotalSteps(uint32_t steps) {
sendHeaderAndCmd(RSP_TOTAL_STEPS);
writeUInt32LE(steps);
Serial.print("TX: TOTAL STEPS = ");
Serial.println(steps);
}
void sendDesiredBearing(uint16_t bearing100) {
sendHeaderAndCmd(RSP_DESIRED_BEARING);
writeUInt16LE(bearing100);
lastSentDesiredBearing100 = bearing100;
Serial.print("TX: DESIRED BEARING x100 = ");
Serial.println(bearing100);
}
void sendObstacleScanRequest() {
sendHeaderAndCmd(RSP_OBS_SCAN_REQUEST);
Serial.println("TX: OBSTACLE SCAN REQUEST");
}
void resetStepperPositions() {
LeftFrontWheel.setCurrentPosition(0);
LeftBackWheel.setCurrentPosition(0);
RightFrontWheel.setCurrentPosition(0);
RightBackWheel.setCurrentPosition(0);
}
void resetNavigationState() {
resetStepperPositions();
totalStepsTaken = 0;
desiredBearing100 = 9000;
actualBearing100 = 9000;
lastSentDesiredBearing100 = 0xFFFF;
bearingDiffTarget = 0;
bearingDiffRamped = 0;
obstacleScanRequested = false;
scanNeutralMode = false;
nextScanStep = scanIntervalSteps;
currentSpeed = 0;
RUN = 0;
Serial.println("Navigation state reset");
Serial.print("desiredBearing100: ");
Serial.println(desiredBearing100);
Serial.print("totalStepsTaken: ");
Serial.println(totalStepsTaken);
Serial.print("combinedStepsDesired: ");
Serial.println(combinedStepsDesired);
Serial.print("pathTotalDistance: ");
Serial.println(pathTotalDistance);
}
void setAllMotorSpeeds(int leftSpeed, int rightSpeed) {
LeftFrontWheel.setSpeed(leftSpeed);
LeftBackWheel.setSpeed(leftSpeed);
RightFrontWheel.setSpeed(rightSpeed);
RightBackWheel.setSpeed(rightSpeed);
}
void stopMotors() {
LeftFrontWheel.stop();
LeftBackWheel.stop();
RightFrontWheel.stop();
RightBackWheel.stop();
LeftFrontWheel.setSpeed(0);
LeftBackWheel.setSpeed(0);
RightFrontWheel.setSpeed(0);
RightBackWheel.setSpeed(0);
Serial.println("Action: stopMotors()");
}
void pivotRight(uint16_t steps) {
long positions[4];
positions[0] = LeftFrontWheel.currentPosition() + steps;
positions[1] = LeftBackWheel.currentPosition() + steps;
positions[2] = RightFrontWheel.currentPosition() - steps;
positions[3] = RightBackWheel.currentPosition() - steps;
multiStepper.moveTo(positions);
while (multiStepper.run()) { }
totalStepsTaken += steps;
stopMotors();
sendTotalSteps(totalStepsTaken);
resetNavigationState();
}
void pivotLeft(uint16_t steps) {
long positions[4];
positions[0] = LeftFrontWheel.currentPosition() - steps;
positions[1] = LeftBackWheel.currentPosition() - steps;
positions[2] = RightFrontWheel.currentPosition() + steps;
positions[3] = RightBackWheel.currentPosition() + steps;
multiStepper.moveTo(positions);
while (multiStepper.run()) { }
totalStepsTaken += steps;
stopMotors();
sendTotalSteps(totalStepsTaken);
resetNavigationState();
}
void beginForwardNavigation() {
RUN = 1;
currentSpeed = 0;
bearingDiffRamped = 0;
obstacleScanRequested = false;
scanNeutralMode = false;
nextScanStep = scanIntervalSteps;
}
void computeDesiredBearing() {
if (bearingAlignment == 0) {
// desiredBearing100 = 9000;
return;
}
uint32_t pathDistanceRemaining = 1;
if (trackDir == 0) {
if (pathTotalDistance > totalStepsTaken) pathDistanceRemaining = pathTotalDistance - totalStepsTaken;
} else {
uint32_t remainingSteps = (combinedStepsDesired > totalStepsTaken) ? (combinedStepsDesired - totalStepsTaken) : 0;
if (pathTotalDistance > remainingSteps) pathDistanceRemaining = pathTotalDistance - remainingSteps;
}
float tangent = (float)oppositeDistance / (float)pathDistanceRemaining;
float deg = atan(tangent) * 180.0 / PI;
desiredBearing100 = (uint16_t)((deg + 90.0) * 100.0);
if (abs((int)desiredBearing100 - (int)lastSentDesiredBearing100) >= 100) {
sendDesiredBearing(desiredBearing100);
}
}
void updateBearingDiff() {
bearingDiffTarget = (int16_t)actualBearing100 - (int16_t)desiredBearing100;
if (bearingDiffRamped < bearingDiffTarget) {
bearingDiffRamped += 10;
if (bearingDiffRamped > bearingDiffTarget) bearingDiffRamped = bearingDiffTarget;
} else if (bearingDiffRamped > bearingDiffTarget) {
bearingDiffRamped -= 10;
if (bearingDiffRamped < bearingDiffTarget) bearingDiffRamped = bearingDiffTarget;
}
}
void runForwardLoop() {
if (currentSpeed < normalSpeed) {
currentSpeed += 10;
if (currentSpeed > normalSpeed) currentSpeed = normalSpeed;
}
computeDesiredBearing();
updateBearingDiff();
int leftSpeed = currentSpeed;
int rightSpeed = currentSpeed;
if (!scanNeutralMode) {
int correction = bearingDiffRamped / 10;
leftSpeed -= correction;
rightSpeed += correction;
}
setAllMotorSpeeds(leftSpeed, rightSpeed);
LeftFrontWheel.runSpeed();
LeftBackWheel.runSpeed();
RightFrontWheel.runSpeed();
RightBackWheel.runSpeed();
totalStepsTaken =
abs(LeftFrontWheel.currentPosition()) +
abs(LeftBackWheel.currentPosition()) +
abs(RightFrontWheel.currentPosition()) +
abs(RightBackWheel.currentPosition()) / 4;
// if (!obstacleScanRequested && totalStepsTaken >= nextScanStep) {
// sendObstacleScanRequest();
// obstacleScanRequested = true;
// scanNeutralMode = true;
// nextScanStep += scanIntervalSteps;
// }
if (totalStepsTaken >= nextScanStep) {
sendObstacleScanRequest();
scanNeutralMode = true;
nextScanStep += scanIntervalSteps;
}
if (totalStepsTaken >= combinedStepsDesired && combinedStepsDesired > 0) {
stopMotors();
sendTotalSteps(totalStepsTaken);
resetNavigationState();
}
}
uint8_t getExpectedLength(uint8_t cmd) {
switch (cmd) {
case CMD_STOP_MOTORS: return 0;
case CMD_ACTUAL_BEARING: return 2;
case CMD_RIGHT_PIVOT: return 2;
case CMD_LEFT_PIVOT: return 2;
case CMD_WAYPOINT_DATA: return 18;
default: return 0xFF;
}
}
void handleWaypointPacket(const uint8_t* data, uint8_t len) {
if (len != 18) return;
pathTotalDistance = readUInt32LE(&data[0]);
combinedStepsDesired = readUInt32LE(&data[4]);
oppositeDistance = readUInt32LE(&data[8]);
bearingAlignment = data[12];
reflectorBearing100 = readUInt16LE(&data[13]);
sensorPrime = data[15];
waypointDesiredBearing100 = readUInt16LE(&data[16]);
waypointActualBearing100 = actualBearing100;
trackDir = 0;
desiredBearing100 = waypointDesiredBearing100;
actualBearing100 = waypointActualBearing100;
sendNavAck();
beginForwardNavigation();
}
void handlePacket(uint8_t cmd, const uint8_t* data, uint8_t len) {
switch (cmd) {
case CMD_STOP_MOTORS:
stopMotors();
sendTotalSteps(totalStepsTaken);
resetNavigationState();
break;
case CMD_ACTUAL_BEARING:
if (len == 2) {
actualBearing100 = readUInt16LE(data);
Serial.print("actualBearing100: ");
Serial.println(actualBearing100);
}
break;
case CMD_RIGHT_PIVOT:
if (len == 2 && RUN == 0) pivotRight(readUInt16LE(data));
break;
case CMD_LEFT_PIVOT:
if (len == 2 && RUN == 0) pivotLeft(readUInt16LE(data));
break;
case CMD_WAYPOINT_DATA:
if (RUN == 0) handleWaypointPacket(data, len);
break;
}
}
void processIncomingByte(uint8_t b) {
switch (state) {
case WAIT_HEADER:
if (b == UART_HEADER) state = WAIT_CMD;
break;
case WAIT_CMD:
currentCmd = b;
expectedLength = getExpectedLength(currentCmd);
if (expectedLength == 0xFF) {
state = WAIT_HEADER;
} else if (expectedLength == 0) {
handlePacket(currentCmd, nullptr, 0);
state = WAIT_HEADER;
} else {
payloadIndex = 0;
state = WAIT_PAYLOAD;
}
break;
case WAIT_PAYLOAD:
payload[payloadIndex++] = b;
if (payloadIndex >= expectedLength) {
handlePacket(currentCmd, payload, expectedLength);
state = WAIT_HEADER;
}
break;
}
}
void readIncomingUart() {
while (Serial3.available() > 0) {
processIncomingByte((uint8_t)Serial3.read());
}
}
void setup() {
Serial.begin(115200);
Serial3.begin(115200);
LeftFrontWheel.setMaxSpeed(2000);
LeftBackWheel.setMaxSpeed(2000);
RightFrontWheel.setMaxSpeed(2000);
RightBackWheel.setMaxSpeed(2000);
LeftFrontWheel.setAcceleration(800);
LeftBackWheel.setAcceleration(800);
RightFrontWheel.setAcceleration(800);
RightBackWheel.setAcceleration(800);
multiStepper.addStepper(LeftFrontWheel);
multiStepper.addStepper(LeftBackWheel);
multiStepper.addStepper(RightFrontWheel);
multiStepper.addStepper(RightBackWheel);
resetNavigationState();
Serial.println("Arduino_v5 ready");
}
void loop() {
readIncomingUart();
if (RUN == 1) {
runForwardLoop();
}
}
Related Hardware (view all EZB hardware)
Related Robot Skill (view all robot skills)
Upgrade to ARC Pro
ARC Pro is more than a tool; it's a creative playground for robot enthusiasts, where you can turn your wildest ideas into reality.
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.

Absolutely - below is a clean ARC JavaScript
UART_v5that matches the corrected 18-byteARDUINO_v5packet exactly, withtrackDirsent as0or1.Packet layout
This sender uses the same payload structure you confirmed:
pathTotalDistance= 4 bytescombinedStepsDesired= 4 bytesoppositeDistance= 4 bytesbearingAlignment= 1 bytedesiredBearing100= 2 bytesactualBearing100= 2 bytestrackDir= 1 byteTotal payload = 18 bytes
ARC JavaScript
UART_v5Important correction on
trackDirI forced
trackDirto be either:01with this logic:
So any nonzero value becomes
1.One thing to verify
Your current Arduino packet layout is now:
pathTotalDistancecombinedStepsDesiredoppositeDistancebearingAlignmentdesiredBearing100actualBearing100trackDirThat is 18 bytes, and the script above matches that.
ARC note
You’re using
Servo.getPosition(d17, 0) * 100as the bearing source. That works ifd17is your intended servo/bearing source in ARC. If you later want the real physical position of a smart servo, we can switch it to:If you want, I can do the next step too
I can also rewrite ARDUINO_v5 to match this ARC sender exactly, including the 18-byte waypoint parser and the same checksum framing, so both sides are fully aligned.
I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
@Athena. Can you please rewrite ARDUINO_v5 to match this ARC sender exactly, including the 18-byte waypoint parser and the same checksum framing, so both sides are fully aligned. Thanks. Jack.
Absolutely - below is a clean ARDUINO_v5 that matches the ARC sender exactly:
0xA5HEADER | CMD | LEN | PAYLOAD | CHECKSUMCMD ^ LEN ^ all payload bytestrackDir: 0 or 1desiredBearing100/actualBearing100: received from ARC in the waypoint packetARDUINO_v5
Important note
This line in
handleWaypointPacket()now treatstrackDiras 0 or 1:That means any incoming value will be reduced to either:
01Packet layout used now
CMD_WAYPOINT_DATApayload = 18 bytespathTotalDistance= bytes0..3combinedStepsDesired= bytes4..7oppositeDistance= bytes8..11bearingAlignment= byte12desiredBearing100= bytes13..14actualBearing100= bytes15..16trackDir= byte17One thing to verify in ARC
Your ARC sender currently says both of these come from:
So
desiredBearing100andactualBearing100will be identical unless you later separate them.If that’s intentional, this Arduino version is aligned with it.
If you want, I can next give you:
I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
@Athena. One thing to note about $actualBearing100 and $desiredBearing100 in ARC (LEG_v5 script).
For WAYPOINT DATA, the initial $actualBearing100 and $desiredBearing100 are the same, Servo.getPosition(d17,0) * 100.
After this, ARC computes $actualBearing100 (IR_TRACKER script). ARDUINO computes $desiredBearing100. They are not read as Servo. positions, but rather (setVar) as global variables. ARC UART_v6 picks these global variables up as ARC and ARDUINO compute them.
Can you rewrite a clean full UART_ v6 with comments removed? Thanks. Jack
Sorry, I can't continue helping in this thread. It's grown long enough that my context window will require too much extra processing, which makes follow-ups unreliable and I can start making mistakes.
A couple of options:
I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
Thank you very much for your help. Jack