@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)
Related Robot Skills (view all robot skills)
Upgrade to ARC Pro
Your robot can be more than a simple automated machine with the power of ARC Pro!
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 cleaned-up rewrite that adds pivot behavior for
CMD_RIGHT_PIVOTandCMD_LEFT_PIVOTwhile keeping the same 2-byte little-endian STEPS payload format.I also kept the rule that pivot commands only run when
navRunning == false.What this version does
CMD_RIGHT_PIVOTCMD_LEFT_PIVOTUses the same packet layout:
HEADERCMDLEN = 2PAYLOAD = steps little-endianCHECKSUMPivot motion is tracked in a separate state so it won’t interfere with forward navigation.
Rewritten sketch
Important note about step counting
I preserved your 2-byte little-endian STEPS payload exactly as requested.
That said, for pivoting I used:
which averages the absolute position of all 4 motors.
Why I did that
Because during a pivot:
So using absolute values makes the pivot progress count up regardless of direction.
One thing to consider
Your current sketch mixes:
AccelStepper::runSpeed()stop()setSpeed()That works for simple speed-based control, but it does not provide a true closed-loop "move exactly N steps" behavior by itself unless you stop based on your own position tracking like we did here.
If you want, I can also give you a more robust version that:
That version is usually better for repeatable pivots.
ARC / EZB side note
Since the EZ-B is acting as a UART bridge, your ARC JavaScript should continue sending the same 2-byte payload format. If you want, I can also help you write the ARC JavaScript packet sender for:
CMD_RIGHT_PIVOTCMD_LEFT_PIVOTusing
Serial.write()/UART.hardwareUartWrite()style patterns in ARC.Quick question
Do you want the pivot to stop based on:
If you want, I can also rewrite this into a cleaner state-machine version with separate
navModeandpivotModehandling.@Athena, I set the Steps to 600. I call for Right Pivot first, then after a 5 sec delay call for Left Pivot. UART is sending the commands, and the Arduino is printing the received commands. The wheels however travel twice as far on the Left Pivot than the Right Pivot.
I need reasonable Step accuracy in distance traveled. I actually intend for inaccuracies to be managed by IR sensor repositioning. Jack
Yes - based on your description, there are two separate issues here:
Also, the fact that
ARDUINO_v8 readyonly sometimes prints usually points to startup timing / reset behavior on Serial3 or USB serial, not necessarily a logic failure in the sketch.1) Are UART buffers and wheel positions being cleared after each pivot?
UART data
No - not explicitly.
Your parser will continue reading bytes as they arrive, but the sketch currently does not clear any buffered UART data at the start or end of a pivot. That usually is not the direct cause of unequal pivot distance if packets are valid, but it can cause stale bytes to remain in the serial buffer if ARC/EZB sends bursts or if a partial packet gets left behind.
For cleaner behavior, I recommend calling:
Serial3.flush()only if you are sure you want to wait for outgoing data to finishExample helper:
Call that:
Wheel positions
No - not in a pivot-specific way.
In the version I gave you, pivot progress was being measured by absolute current step positions, but the step positions themselves were not reset at pivot start/end.
That means if one pivot begins with existing motor offsets, you can get inconsistent results.
For better repeatability, you should store the starting position of each wheel when the pivot begins, then measure delta from start.
That is the right way to get consistent step accuracy.
2) Why Left Pivot may travel farther than Right Pivot
The most likely reason is that the current progress check was based on this kind of logic:
labs(currentPosition())across all motorsThat can be inaccurate if:
stop()decelerates differently depending on directionBetter approach
For pivots, store the starting position of each motor:
pivotStartLFpivotStartLBpivotStartRFpivotStartRBThen compute progress as:
abs(currentPosition - startPosition)for each wheelThat gives much better symmetry.
3) Why
ARDUINO_v8 readyonly prints occasionallyThis usually means one of these:
Serial3or USB serial timing is inconsistent after uploadImportant
If you are viewing that print over the USB serial monitor, note that on Arduino Mega uploads often reset the board and the serial port may take a moment to reconnect. So an "occasional" startup message can happen even when the sketch is fine.
However, if the print was previously reliable and now isn’t, I would add a short startup delay before
Serial.println():That often stabilizes startup logging.
4) What I recommend changing
A. Add explicit pivot start positions
Use pivot-specific start values instead of raw absolute position.
B. Clear incoming UART before each pivot
This avoids stale packet fragments.
C. Reset pivot state after pivot completes
Set pivot mode back to none, zero out pivot counters, and optionally clear wheel positions if you want the next pivot measured fresh.
D. Add a startup delay before printing ready message
This should help the irregular startup print.
5) Example of the pivot logic you should use
Here is the core idea:
Then progress:
This is much more reliable than using absolute positions.
6) Best practice for stopping pivots
Instead of relying only on
stop()and then leaving the positions untouched, do this when pivot completes:If you want the next pivot to start from a clean zero reference, this is a good choice.
7) My recommendation for your use case
Since you said:
That means your firmware should aim for:
So I would recommend:
8) Follow-up question
Do you want me to rewrite the full sketch now with these fixes applied?
I can give you a cleaned-up version that includes:
ARDUINO_v8 readyIf you want, I’ll post the complete revised sketch in one block.
Yes @Athena, please give a cleaned-up version that includes:
ARDUINO_v8 readyLet's call it Arduino_v9. Thank you. Jack
Absolutely, Jack - here’s a cleaned-up Arduino_v9 sketch with the fixes you asked for:
ARDUINO_v9 readyI also kept your 2-byte little-endian STEPS payload exactly as requested.
Arduino_v9
Notes on the changes
1) Pivot symmetry
The biggest change is this:
That should help a lot with the "right pivot behaves differently from left pivot" issue.
2) Pivot reset behavior
After a pivot completes, it now does all of this:
stopMotors()clearIncomingUart()resetStepperPositions()resetPivotState()So each pivot starts clean.
3) Startup message reliability
I added:
before serial startup logging. That usually helps make the
ARDUINO_v9 readymessage much more consistent after upload/reset.One important practical note
Because the motors are being driven open-loop, perfect matching of left/right pivot distance is still going to depend on:
So your plan to correct with IR repositioning is a good one.
If you want the next improvement
I can also make one more refinement for you:
That can make step accuracy even more predictable.
If you want, I can also help you with the ARC JavaScript packet sender for these pivot commands so it matches this sketch exactly.
Thank you @Athena. The Arduino_v9 works very well. Pivot distances are repeatable and exactly what I was looking for. Can you please review UART_v8, the (ARC JavaScript packet sender) for these pivot commands, so it matches this sketch exactly? Let's call this UART_v9. Thank you. Jack.
UART_v8
Absolutely, Jack - here’s a cleaned-up UART_v9 that matches Arduino_v9 exactly for the pivot commands, including the same 2-byte little-endian STEPS payload.
What I changed
0xA5headercmdlenpayloadchecksum = cmd ^ len ^ payload...UART_v9
Notes
Pivot format matches Arduino_v9
This is the important part:
CMD_RIGHT_PIVOTuses:payload = [steps low byte, steps high byte]CMD_LEFT_PIVOTuses:So it will match the Arduino sketch exactly.
One small improvement
I added
bytesToU32LE()because your RX handler used it but it wasn’t defined in the pasted script.Suggested ARC variables
This script expects these variables to exist in your ARC project:
$pivotSteps$sendRightPivotNow$sendLeftPivotNow$sendWaypointNow$waypointSent$sendStopNow$stopSent$actualBearing100$desiredBearing100$pathTotalDistance$combinedStepsDesired$oppositeDistance$bearingAlignment$trackDir$scanIntervalSteps$navAckReceived$totalStepsTaken$legComplete$obsScanRequested$obsScanActiveOne thing to consider
Right now, this sender assumes your ARC script sets
$pivotStepsbefore triggering either pivot command. If you want, I can also make a slightly nicer UART_v10 where:sendRightPivot(steps)andsendLeftPivot(steps)take the step count directly$pivotStepsIf you want that, I can rewrite it next.
Thank you @Athena, the UART_v9 pairs nicely with the Arduino_v9. I am experiencing difficulty with Arduino_v9 in the speed ramp-up function and the overall 'torquiness' of the motors. The ramp up to normal speed does not seem to work. Printing confirms that PWM 600 for left and right motors begin at start. [ applyForwardMotion()] Additionally, I wanted to soften the speed transition when a new actualBearing100 is received and so reduced the bearingDiffRamped to 1, from a 10. [updateBearingDiff()] I adjusted ARC script "IR_TRACKER_v3" to minimize changes to actualBearing100 to assist in minimizing the bearingDiffTarget in Arduino. Can you please review the posted Arduino_v9 below and rewrite it to smoothly ramp up to normal speed and not be so aggressive with PWM changes? Let's call it Arduino_v10. Thank you. Jack Arduino_v9