@Athena,
I'm using the Arduino Mega sketch below to control four stepper motors via AccelStepper / MultiStepper. The Arduino is connected to an EZB4 over UART (Serial3). The EZB4 is running JavaScript uploaded from ARC and is sending CMD_RIGHT_PIVOT and CMD_LEFT_PIVOT packets with a STEPS value over UART to the Arduino.
Can you rewrite the sketch to implement the following behavior?
- When receiving a pivot command (CMD_RIGHT_PIVOT or CMD_LEFT_PIVOT), pivot the appropriate side so the robot rotates in that direction:
- CMD_RIGHT_PIVOT: drive the left motors (both left wheels) forward and drive the right motors in reverse so the robot pivots to the right.
- CMD_LEFT_PIVOT: drive the right motors (both right wheels) forward and drive the left motors in reverse so the robot pivots to the left.
- Count the STEPS value received in the pivot packet and stop the motors (call stopMotors()) once the pivot STEPS count is reached.
- The current code checks pivot packet length == 2, so I expect the STEPS payload is a 2-byte little-endian value. Please use that same format unless you indicate otherwise.
For context: the Arduino sketch handles navigation and other packets (waypoints, bearing updates, etc.). The EZB4 is acting as the UART bridge between ARC (Robot Skills / JavaScript on the EZB) and this Arduino Mega sketch. The pivot commands should only act when not already in forward navigation (navRunning == false), consistent with the current sketch.
Below is the current sketch I'm using. Please rewrite it to add the pivot behavior described above.
#include <AccelStepper.h>
#include <MultiStepper.h>
#include <math.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;
static const uint8_t MAX_PAYLOAD = 32;
// -------------------------
// Motor setup
// -------------------------
AccelStepper LeftFrontWheel(AccelStepper::DRIVER, 3, 6);
AccelStepper LeftBackWheel(AccelStepper::DRIVER, 2, 5);
AccelStepper RightFrontWheel(AccelStepper::DRIVER, 12, 13);
AccelStepper RightBackWheel(AccelStepper::DRIVER, 4, 7);
MultiStepper multiStepper;
// -------------------------
// Parser
// -------------------------
enum ParserState {
WAIT_HEADER,
WAIT_CMD,
WAIT_LEN,
WAIT_PAYLOAD,
WAIT_CHECKSUM
};
ParserState parserState = WAIT_HEADER;
uint8_t currentCmd = 0;
uint8_t expectedLen = 0;
uint8_t payloadIndex = 0;
uint8_t payload[MAX_PAYLOAD];
uint8_t runningChecksum = 0;
// -------------------------
// Navigation state
// -------------------------
bool navRunning = false;
uint32_t totalStepsTaken = 0;
uint32_t combinedStepsDesired = 0;
uint32_t pathTotalDistance = 0;
int32_t oppositeDistance = 0;
uint32_t nextScanStep = 0;
uint32_t scanIntervalSteps = 3056;
uint8_t bearingAlignment = 0;
uint8_t trackDir = 0;
uint16_t actualBearing100 = 9000;
uint16_t desiredBearing100 = 9000;
uint16_t lastSentDesiredBearing100 = 0xFFFF;
int16_t bearingDiffTarget = 0;
int16_t bearingDiffRamped = 0;
uint16_t currentSpeed = 0;
uint16_t normalSpeed = 600; //800
// -------------------------
// Helpers
// -------------------------
uint16_t readUInt16LE(const uint8_t* data) {
return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}
uint32_t readUInt32LE(const uint8_t* data) {
return (uint32_t)data[0]
| ((uint32_t)data[1] << 8)
| ((uint32_t)data[2] << 16)
| ((uint32_t)data[3] << 24);
}
void sendPacket(uint8_t cmd, const uint8_t* data, uint8_t len) {
uint8_t checksum = (uint8_t)(cmd ^ len);
Serial3.write(UART_HEADER);
Serial3.write(cmd);
Serial3.write(len);
for (uint8_t i = 0; i < len; i++) {
Serial3.write(data[i]);
checksum ^= data[i];
}
Serial3.write(checksum);
}
void sendCmdOnly(uint8_t cmd) {
sendPacket(cmd, nullptr, 0);
}
void sendNavAck() {
sendCmdOnly(RSP_NAV_ACK);
Serial.println(F("TX: NAV ACK"));
}
void sendTotalSteps(uint32_t steps) {
uint8_t tmp[4];
tmp[0] = (uint8_t)(steps & 0xFF);
tmp[1] = (uint8_t)((steps >> 8) & 0xFF);
tmp[2] = (uint8_t)((steps >> 16) & 0xFF);
tmp[3] = (uint8_t)((steps >> 24) & 0xFF);
sendPacket(RSP_TOTAL_STEPS, tmp, 4);
Serial.print(F("TX: TOTAL STEPS = "));
Serial.println(steps);
}
void sendDesiredBearing(uint16_t bearing100) {
uint8_t tmp[2];
tmp[0] = (uint8_t)(bearing100 & 0xFF);
tmp[1] = (uint8_t)((bearing100 >> 8) & 0xFF);
sendPacket(RSP_DESIRED_BEARING, tmp, 2);
lastSentDesiredBearing100 = bearing100;
Serial.print(F("TX: DESIRED BEARING x100 = "));
Serial.println(bearing100);
}
void sendObstacleScanRequest() {
sendCmdOnly(RSP_OBS_SCAN_REQUEST);
Serial.println(F("TX: OBSTACLE SCAN REQUEST"));
}
void resetStepperPositions() {
LeftFrontWheel.setCurrentPosition(0);
LeftBackWheel.setCurrentPosition(0);
RightFrontWheel.setCurrentPosition(0);
RightBackWheel.setCurrentPosition(0);
}
uint32_t getAverageAbsStepperPosition() {
uint32_t a = (uint32_t)labs(LeftFrontWheel.currentPosition());
uint32_t b = (uint32_t)labs(LeftBackWheel.currentPosition());
uint32_t c = (uint32_t)labs(RightFrontWheel.currentPosition());
uint32_t d = (uint32_t)labs(RightBackWheel.currentPosition());
return (a + b + c + d) / 4;
}
void setAllMotorSpeeds(int leftSpeed, int rightSpeed) {
leftSpeed = constrain(leftSpeed, 0, 2000);
rightSpeed = constrain(rightSpeed, 0, 2000);
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(F("Action: stopMotors()"));
}
void resetNavigationState() {
resetStepperPositions();
totalStepsTaken = 0;
desiredBearing100 = 9000;
actualBearing100 = 9000;
lastSentDesiredBearing100 = 0xFFFF;
bearingDiffTarget = 0;
bearingDiffRamped = 0;
nextScanStep = scanIntervalSteps;
currentSpeed = 0;
navRunning = false;
Serial.println(F("Navigation state reset"));
}
void beginForwardNavigation() {
navRunning = true;
currentSpeed = 0;
bearingDiffRamped = 0;
nextScanStep = scanIntervalSteps;
Serial.println(F("Navigation started"));
}
// -------------------------
// Navigation math
// -------------------------
void computeDesiredBearing() {
if (bearingAlignment == 0) {
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;
}
}
if (pathDistanceRemaining == 0) {
pathDistanceRemaining = 1;
}
bool reflectorIsLeft = (desiredBearing100 >= 9100);
int32_t signedOppositeDistance = (int32_t)oppositeDistance;
if (reflectorIsLeft) {
signedOppositeDistance = -labs(signedOppositeDistance);
} else {
signedOppositeDistance = labs(signedOppositeDistance);
}
float angleDeg = atan2((float)signedOppositeDistance, (float)pathDistanceRemaining) * 180.0f / PI;
int32_t bearing100 = (int32_t)((90.0f + angleDeg) * 100.0f);
if (bearing100 < 0) bearing100 = 0;
if (bearing100 > 18000) bearing100 = 18000;
desiredBearing100 = (uint16_t)bearing100;
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 applyForwardMotion() {
int leftSpeed = normalSpeed;
int rightSpeed = normalSpeed;
int absDiff = abs(bearingDiffRamped);
if (bearingDiffRamped > 0) {
leftSpeed = normalSpeed - (absDiff * 2);
rightSpeed = normalSpeed + (absDiff * 2);
} else if (bearingDiffRamped < 0) {
leftSpeed = normalSpeed + (absDiff * 2);
rightSpeed = normalSpeed - (absDiff * 2);
}
setAllMotorSpeeds(leftSpeed, rightSpeed);
// Serial.print("leftSpeed: ");
// Serial.println(leftSpeed);
// Serial.print("rightSpeed: ");
// Serial.println(rightSpeed);
}
void updateObstacleScanSchedule() {
if (scanIntervalSteps == 0) {
return;
}
if (totalStepsTaken >= nextScanStep) {
sendObstacleScanRequest();
nextScanStep += scanIntervalSteps;
}
}
void runForwardLoop() {
if (!navRunning) {
return;
}
computeDesiredBearing();
updateBearingDiff();
applyForwardMotion();
LeftFrontWheel.runSpeed();
LeftBackWheel.runSpeed();
RightFrontWheel.runSpeed();
RightBackWheel.runSpeed();
totalStepsTaken = getAverageAbsStepperPosition();
updateObstacleScanSchedule();
if (combinedStepsDesired > 0 && totalStepsTaken >= combinedStepsDesired) {
stopMotors();
sendTotalSteps(totalStepsTaken);
resetNavigationState();
}
}
// -------------------------
// Packet handlers
// -------------------------
void handleWaypointPacket(const uint8_t* data, uint8_t len) {
if (len != 22) {
Serial.print(F("Waypoint length error: "));
Serial.println(len);
return;
}
pathTotalDistance = readUInt32LE(&data[0]);
combinedStepsDesired = readUInt32LE(&data[4]);
oppositeDistance = (int32_t)readUInt32LE(&data[8]);
bearingAlignment = data[12];
desiredBearing100 = readUInt16LE(&data[13]);
actualBearing100 = readUInt16LE(&data[15]);
trackDir = data[17] & 0x01;
scanIntervalSteps = readUInt32LE(&data[18]);
Serial.println(F("RX: WAYPOINT DATA"));
Serial.print(F(" pathTotalDistance=")); Serial.println(pathTotalDistance);
Serial.print(F(" combinedStepsDesired=")); Serial.println(combinedStepsDesired);
Serial.print(F(" oppositeDistance=")); Serial.println(oppositeDistance);
Serial.print(F(" bearingAlignment=")); Serial.println(bearingAlignment);
Serial.print(F(" desiredBearing100=")); Serial.println(desiredBearing100);
Serial.print(F(" actualBearing100=")); Serial.println(actualBearing100);
Serial.print(F(" trackDir=")); Serial.println(trackDir);
Serial.print(F(" scanIntervalSteps=")); Serial.println(scanIntervalSteps);
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(F("RX: ACTUAL BEARING x100 = "));
Serial.println(actualBearing100);
}
break;
case CMD_RIGHT_PIVOT:
if (len == 2 && !navRunning) {
Serial.println(F("RX: RIGHT PIVOT"));
}
break;
case CMD_LEFT_PIVOT:
if (len == 2 && !navRunning) {
Serial.println(F("RX: LEFT PIVOT"));
}
break;
case CMD_WAYPOINT_DATA:
if (!navRunning) {
handleWaypointPacket(data, len);
}
break;
}
}
// -------------------------
// UART parser
// -------------------------
void processIncomingByte(uint8_t b) {
switch (parserState) {
case WAIT_HEADER:
if (b == UART_HEADER) parserState = WAIT_CMD;
break;
case WAIT_CMD:
currentCmd = b;
runningChecksum = b;
parserState = WAIT_LEN;
break;
case WAIT_LEN:
expectedLen = b;
runningChecksum ^= b;
if (expectedLen > MAX_PAYLOAD) {
parserState = WAIT_HEADER;
} else if (expectedLen == 0) {
parserState = WAIT_CHECKSUM;
} else {
payloadIndex = 0;
parserState = WAIT_PAYLOAD;
}
break;
case WAIT_PAYLOAD:
payload[payloadIndex++] = b;
runningChecksum ^= b;
if (payloadIndex >= expectedLen) {
parserState = WAIT_CHECKSUM;
}
break;
case WAIT_CHECKSUM:
if (b == runningChecksum) {
handlePacket(currentCmd, payload, expectedLen);
} else {
Serial.println(F("Checksum error"));
}
parserState = WAIT_HEADER;
runningChecksum = 0;
currentCmd = 0;
expectedLen = 0;
payloadIndex = 0;
break;
}
}
void readIncomingUart() {
while (Serial3.available() > 0) {
processIncomingByte((uint8_t)Serial3.read());
}
}
// -------------------------
// Arduino entry points
// -------------------------
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);
LeftFrontWheel.setSpeed(0);
LeftBackWheel.setSpeed(0);
RightFrontWheel.setSpeed(0);
RightBackWheel.setSpeed(0);
multiStepper.addStepper(LeftFrontWheel);
multiStepper.addStepper(LeftBackWheel);
multiStepper.addStepper(RightFrontWheel);
multiStepper.addStepper(RightBackWheel);
resetNavigationState();
Serial.println(F("ARDUINO_v8 ready"));
}
void loop() {
readIncomingUart();
runForwardLoop();
}
Thanks, Jack
Related Hardware (view all EZB hardware)
