Asked
,
#include <AccelStepper.h>#include <MultiStepper.h>
static const uint8_t UART_HEADER = 0xA5;
// ARC -> Arduinostatic 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 -> ARCstatic const uint8_t RSP_NAV_ACK = 0x10;static const uint8_t RSP_TOTAL_STEPS = 0x11;static const uint8_t RSP_DESIRED_BEARING = 0x12;static const uint8_t RSP_OBS_SCAN_REQUEST = 0x13;
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());
if (!obstacleScanRequested && totalStepsTaken >= nextScanStep) { sendObstacleScanRequest(); obstacleScanRequested = true; 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); } 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(); }}
This is an Arduino sketch that communicates over Serial3 using a simple UART protocol (header 0xA5). It should trigger sendObstacleScanRequest() every 10,500 steps, but it only seems to occur at the first 10,500-step mark and not subsequently. Is this line of code correct?
if (!obstacleScanRequested && totalStepsTaken >= nextScanStep) { sendObstacleScanRequest(); obstacleScanRequested = true; scanNeutralMode = true; nextScanStep += scanIntervalSteps; }
For reference, Serial3 is the serial port used to communicate with ARC (or a host controller/EZB) in this setup.
Thanks, Jack
Related Hardware (view all EZB hardware)
Arduino Due/Mega
by Arduino
Synthiam ARC enables Arduino Mega 2560 control with 8 ADC, 13 PWM, 21 digital ports and 24 servos; includes firmware, shield and tutorials
USB
Servos
24
Camera
Audio
UART
3
I2C
ADC
8
Digital
21
Related Robot Skills (view all robot skills)
Script
by Synthiam
Multi-language ARC Script: build, run and debug Blockly, JavaScript, EZ-Script or Python with Intellisense, run/save/load and Roboscratch support
NMS Navigation Data Grabber
by Synthiam Inc.
Query NMS lidar scan data by angle, with control commands for range, arc extremes, and obstacle checks from the latest scan.
Requires ARC
v2

Yes - the issue is very likely that
obstacleScanRequestedis never being cleared back tofalseafter the first request.Right now your logic does this:
That means:
obstacleScanRequestedbecomestruefalseWhy it only happens once
Your code increments
nextScanStep, but the!obstacleScanRequestedgate blocks future requests forever.Fix options
Option 1: Reset the flag when the scan response is received
If ARC or your host sends a response back after the scan, that response handler should clear the flag:
That would allow the next 10,500-step trigger to fire.
Option 2: Remove the flag entirely
If you only want one request per interval, the
nextScanStepcheck is enough by itself:This works only if
runForwardLoop()runs often enough and you don’t need a "wait for response" lockout.Option 3: Use a response/timeout state machine
A safer approach is:
obstacleScanRequested = trueobstacleScanRequestedThat’s usually the best pattern for serial robot navigation.
One more thing to check
You calculate:
If your steppers are not all moving the way you expect, this could jump in unexpected ways. But that would not explain "only once" as clearly as the boolean flag does.
Recommended change
If you want repeated obstacle scan requests every 10,500 steps, I’d start with this:
Then, wherever you process the scan reply:
ARC side note
If this Arduino is talking to ARC over
Serial3, make sure your ARC side skill is reading the custom protocol consistently. In ARC, the best place to handle this is usually a Script skill using JavaScript or Python, or a custom robot skill if you’re building a reusable interface.If you want, I can help you turn this into a cleaner state machine so the scan request/reply cycle is reliable and easier to debug.
Also, if you’d like, I can review the
runForwardLoop()portion too - there may be a second issue there affecting the repeat trigger.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.