Asked
— Edited
Hi @Athena,
I'm trying to make an ARC UART script that works seamlessly with an Arduino Mega. Both scripts are reproduced below. A few important constraints and clarifications:
- For WAYPOINT DATA only: in ARC the variables $actualBearing100 and $desiredBearing100 must be the same value and equal to Servo.getPosition(d17, 0) * 100.
- After WAYPOINT DATA is sent by the ARC UART script (UART_v6), ARC (my IR_TRACKER robot skill / script) computes $actualBearing100. The Arduino computes $desiredBearing100. These values are not read as servo positions on the Arduino - they are assigned and exchanged via ARC global variables (setVar / getVar).
- The ARC UART_v6 robot skill picks up and sets ARC globals (getVar / setVar). The Arduino communicates over Serial3 in the sketch. The connection model appears to be a hardware UART between ARC (JavaScript running as a Robot Skill) and the Arduino Mega (Serial3).
Request:
- Can you rewrite a clean, full UART_v6 script (comments removed) that incorporates the difference in how $actualBearing and $desiredBearing are produced, and that matches exactly the accompanying ARDUINO_v6 sketch?
Thank you, Jack
ARDUINO_v6
#include <AccelStepper.h>
#include <MultiStepper.h>
// =====================
// Protocol
// =====================
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;
// =====================
// 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);
MultiStepper multiStepper;
// =====================
// Parser state
// =====================
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;
bool scanNeutralMode = false;
uint32_t totalStepsTaken = 0;
uint32_t combinedStepsDesired = 0;
uint32_t pathTotalDistance = 0;
uint32_t oppositeDistance = 0;
uint32_t nextScanStep = 10500;
const uint32_t scanIntervalSteps = 10500;
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 = 800;
// =====================
// Byte 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 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));
}
// =====================
// Protocol helpers
// =====================
uint8_t checksumXor(uint8_t cmd, uint8_t len, const uint8_t* data) {
uint8_t c = cmd ^ len;
for (uint8_t i = 0; i < len; i++) {
c ^= data[i];
}
return c;
}
void sendPacket(uint8_t cmd, const uint8_t* data, uint8_t len) {
Serial3.write(UART_HEADER);
Serial3.write(cmd);
Serial3.write(len);
for (uint8_t i = 0; i < len; i++) {
Serial3.write(data[i]);
}
Serial3.write(checksumXor(cmd, len, data));
}
void sendCmdOnly(uint8_t cmd) {
sendPacket(cmd, nullptr, 0);
}
void sendNavAck() {
sendCmdOnly(RSP_NAV_ACK);
Serial.println("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("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("TX: DESIRED BEARING x100 = ");
Serial.println(bearing100);
}
void sendObstacleScanRequest() {
sendCmdOnly(RSP_OBS_SCAN_REQUEST);
Serial.println("TX: OBSTACLE SCAN REQUEST");
}
// =====================
// Motor helpers
// =====================
void resetStepperPositions() {
LeftFrontWheel.setCurrentPosition(0);
LeftBackWheel.setCurrentPosition(0);
RightFrontWheel.setCurrentPosition(0);
RightBackWheel.setCurrentPosition(0);
}
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("Action: stopMotors()");
}
void resetNavigationState() {
resetStepperPositions();
totalStepsTaken = 0;
desiredBearing100 = 9000;
actualBearing100 = 9000;
lastSentDesiredBearing100 = 0xFFFF;
bearingDiffTarget = 0;
bearingDiffRamped = 0;
scanNeutralMode = false;
nextScanStep = scanIntervalSteps;
currentSpeed = 0;
navRunning = false;
Serial.println("Navigation state reset");
}
void beginForwardNavigation() {
navRunning = true;
currentSpeed = 0;
bearingDiffRamped = 0;
scanNeutralMode = false;
nextScanStep = scanIntervalSteps;
}
// =====================
// 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;
}
}
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 (!navRunning) {
return;
}
if (currentSpeed < normalSpeed) {
currentSpeed = normalSpeed;
}
computeDesiredBearing();
updateBearingDiff();
int leftSpeed = currentSpeed;
int rightSpeed = currentSpeed;
if (!scanNeutralMode) {
int correction = bearingDiffRamped / 10;
leftSpeed -= correction;
rightSpeed += correction;
}
leftSpeed = constrain(leftSpeed, 0, 2000);
rightSpeed = constrain(rightSpeed, 0, 2000);
Serial.print("RUN=");
Serial.print(navRunning);
Serial.print(" currentSpeed=");
Serial.print(currentSpeed);
Serial.print(" bearingDiffRamped=");
Serial.print(bearingDiffRamped);
Serial.print(" leftSpeed=");
Serial.print(leftSpeed);
Serial.print(" rightSpeed=");
Serial.print(rightSpeed);
Serial.print(" scanNeutralMode=");
Serial.println(scanNeutralMode);
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 (totalStepsTaken >= nextScanStep) {
sendObstacleScanRequest();
scanNeutralMode = true;
nextScanStep += scanIntervalSteps;
}
if (combinedStepsDesired > 0 && totalStepsTaken >= combinedStepsDesired) {
stopMotors();
sendTotalSteps(totalStepsTaken);
resetNavigationState();
}
}
// =====================
// Packet handling
// =====================
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 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 handleWaypointPacket(const uint8_t* data, uint8_t len) {
if (len != 18) {
Serial.println("Waypoint length error");
return;
}
pathTotalDistance = readUInt32LE(&data[0]);
combinedStepsDesired = readUInt32LE(&data[4]);
oppositeDistance = readUInt32LE(&data[8]);
bearingAlignment = data[12];
desiredBearing100 = readUInt16LE(&data[13]);
actualBearing100 = readUInt16LE(&data[15]);
trackDir = data[17] & 0x01;
Serial.println("RX: WAYPOINT DATA");
Serial.print(" pathTotalDistance=");
Serial.println(pathTotalDistance);
Serial.print(" combinedStepsDesired=");
Serial.println(combinedStepsDesired);
Serial.print(" oppositeDistance=");
Serial.println(oppositeDistance);
Serial.print(" bearingAlignment=");
Serial.println(bearingAlignment);
Serial.print(" desiredBearing100=");
Serial.println(desiredBearing100);
Serial.print(" actualBearing100=");
Serial.println(actualBearing100);
Serial.print(" trackDir=");
Serial.println(trackDir);
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 && !navRunning) {
pivotRight(readUInt16LE(data));
}
break;
case CMD_LEFT_PIVOT:
if (len == 2 && !navRunning) {
pivotLeft(readUInt16LE(data));
}
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("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 setup/loop
// =====================
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();
runForwardLoop();
}
UART_v6 (ARC JavaScript Robot Skill)
var UART_INDEX = 0;
var UART_HEADER = 0xA5;
// ARC -> Arduino
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;
// Arduino -> ARC
var RSP_NAV_ACK = 0x10;
var RSP_TOTAL_STEPS = 0x11;
var RSP_DESIRED_BEARING = 0x12;
var RSP_OBS_SCAN_REQUEST = 0x13;
// RX parser state
var rxState = 0; // 0=wait header, 1=cmd, 2=len, 3=payload, 4=checksum
var rxCmd = 0;
var rxLen = 0;
var rxIndex = 0;
var rxPayload = [];
var rxChecksum = 0;
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)
) >>> 0;
}
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 cs = checksumXor(cmd, len, payload);
var out = [];
out.push(UART_HEADER);
out.push(cmd & 0xFF);
out.push(len);
for (var i = 0; i < payload.length; i++) {
out.push(payload[i] & 0xFF);
}
out.push(cs);
UART.hardwareUartWrite(UART_INDEX, out);
}
function sendCmdOnly(cmd) {
sendPacket(cmd, []);
}
function sendStopMotors() {
sendCmdOnly(CMD_STOP_MOTORS);
print("TX: STOP MOTORS");
}
function sendActualBearing() {
// You asked for bearing to reflect Servo.getPosition(d17,0) * 100
// var bearing100 = safeInt(Servo.getPosition(d17, 0), 90) * 100;
var bearing100 = getVar("$actualBearing100");
bearing100 = clamp(bearing100, 0, 18000);
var payload = u16ToBytes(bearing100);
sendPacket(CMD_ACTUAL_BEARING, payload);
print("TX: ACTUAL BEARING x100 = " + bearing100);
}
function sendWaypointData() {
// Bearing source per your request
var bearing100 = safeInt(Servo.getPosition(d17, 0), 90) * 100;
bearing100 = clamp(bearing100, 0, 18000);
// Build packet values from ARC globals
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;
// trackDir must be 0 or 1
var trackDir = safeInt(getVar("$trackDir", 0), 0);
trackDir = (trackDir === 0) ? 0 : 1;
// Per your request: both values reflect Servo.getPosition(d17,0) * 100
var desiredBearing100 = bearing100;
var actualBearing100 = bearing100;
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);
if (payload.length != 18) {
print("Waypoint payload length error: " + payload.length);
return;
}
sendPacket(CMD_WAYPOINT_DATA, payload);
print("TX: WAYPOINT DATA");
print(" pathTotalDistance=" + pathTotalDistance);
print(" combinedStepsDesired=" + combinedStepsDesired);
print(" oppositeDistance=" + oppositeDistance);
print(" bearingAlignment=" + bearingAlignment);
print(" desiredBearing100=" + desiredBearing100);
print(" actualBearing100=" + actualBearing100);
print(" trackDir=" + trackDir);
}
function sendRightPivot() {
var steps = safeInt(getVar("$pivotSteps", 0), 0) >>> 0;
sendPacket(CMD_RIGHT_PIVOT, u16ToBytes(steps));
print("TX: RIGHT PIVOT steps=" + steps);
}
function sendLeftPivot() {
var steps = safeInt(getVar("$pivotSteps", 0), 0) >>> 0;
sendPacket(CMD_LEFT_PIVOT, u16ToBytes(steps));
print("TX: LEFT PIVOT steps=" + steps);
}
function expectedLengthForCmd(cmd) {
switch (cmd) {
case RSP_NAV_ACK:
return 0;
case RSP_TOTAL_STEPS:
return 4;
case RSP_DESIRED_BEARING:
return 2;
case RSP_OBS_SCAN_REQUEST:
return 0;
default:
return -1;
}
}
function handleRxPacket(cmd, payload) {
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]);
print("RX: TOTAL STEPS = " + steps);
setVar("$totalStepsTaken", steps);
}
return;
}
if (cmd == RSP_DESIRED_BEARING) {
if (payload.length == 2) {
var bearing100 = bytesToU16LE(payload[0], payload[1]);
print("RX: DESIRED BEARING x100 = " + bearing100);
setVar("$desiredBearing100", bearing100);
}
return;
}
if (cmd == RSP_OBS_SCAN_REQUEST) {
print("RX: OBSTACLE SCAN REQUEST");
setVar("$obstacleScanRequested", 1);
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];
available--;
if (rxState == 0) {
if (b == UART_HEADER) {
rxState = 1;
}
continue;
}
if (rxState == 1) {
rxCmd = b & 0xFF;
rxChecksum = rxCmd;
rxState = 2;
continue;
}
if (rxState == 2) {
rxLen = b & 0xFF;
rxChecksum ^= rxLen;
rxPayload = [];
rxIndex = 0;
if (rxLen == 0) {
rxState = 4;
} else {
rxState = 3;
}
continue;
}
if (rxState == 3) {
rxPayload.push(b & 0xFF);
rxChecksum ^= (b & 0xFF);
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("UART_v5 started");
while (true) {
if (getVar("$sendWaypointNow", 0) == 1 && getVar("$waypointSent", 0) == 0) {
sendWaypointData();
setVar("$sendWaypointNow", 0);
setVar("$waypointSent", 1);
}
if (getVar("$sendStopNow", 0) == 1) {
sendStopMotors();
setVar("$sendStopNow", 0);
}
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);
}
Related Hardware (view all EZB hardware)
EZ-B V4
by EZ-Robot
EZ-B v4/2 robot controller: dual Cortex ARM, Wi-Fi, audio/video, 24 servo/digital ports, I2C/UART, camera and Synthiam ARC control for custom robots
Wi-Fi / USB
Servos
24
Camera
Audio
UART
3
I2C
ADC
8
Digital
24
Related Robot Skills (view all robot skills)
Slide Script
by Synthiam
Adjust a numeric variable via slider (0-1000) with customizable min/max, center button and scripts triggered on value change or bonus button press.
Task Script
by Synthiam Inc.
Split complex robot tasks into sequential scripts, synchronizing asynchronous skills via NEXT/CANCEL commands for staged automation.
Requires ARC
v8
Upgrade to ARC Pro
Your robot can be more than a simple automated machine with the power of ARC Pro!
📚 This thread has been preserved as a helpful resource!
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 - here’s a review of the math in
computeDesiredBearing()andupdateBearingDiff(), plus the key things I’d change for consistency and safety in ARC/Arduino.computeDesiredBearing()What it’s doing now
You’re computing:
pathDistanceRemainingoppositeDistance / pathDistanceRemainingatan(ratio)in degrees+ 90and scaling by* 100So this line:
means your bearing range becomes roughly:
0to18000for0.00to180.00That’s consistent with your
bearing100protocol.Main concern: divide-by-zero / near-zero
This line is vulnerable:
You guard
pathDistanceRemainingby setting it to1initially, which avoids divide-by-zero, but it can still create a very large angle if the remaining distance is extremely small.That may be okay if intentional, but if not, I’d clamp it more explicitly.
Main concern: use of
atan()instead ofatan2()If
oppositeDistanceis always positive and you only need a single-sided geometry angle,atan()is fine.But if you want a more robust bearing calculation,
atan2()is usually better because it handles sign and quadrants properly.For your current design, though,
atan()is acceptable if the geometry is always positive and one-directional.Suggested cleaner version
updateBearingDiff()What it’s doing now
You calculate:
This means:
That’s good and intuitive.
Then you "ramp" the difference:
This is essentially a smoothing filter. That’s a good idea.
Main concern: overshoot
Right now, if
bearingDiffRampedis close tobearingDiffTarget, adding or subtracting10can overshoot the target.For example:
7010, which overshootsYou partially correct this by forcing:
But only after the increment/decrement logic. That works, though it can be made cleaner.
Cleaner version
This version prevents overshoot directly:
Important logic note
Right now
updateBearingDiff()only computes a smoothed difference. It does not actually use that difference to drive any correction behavior in the snippet you shared.So the math is fine, but the next question is:
bearingDiffRampedis positive?That’s the part that determines whether the math is merely computed or actually used for navigation correction.
My recommendation
Keep:
desiredBearing100 = (deg + 90.0) * 100.0bearingDiffTarget = actual - desiredImprove:
pathDistanceRemaining <= 0desiredBearing100to0..18000updateBearingDiff()One more small ARC/Arduino consistency note
Because ARC is sending
actualBearing100initially and later sending computed actual bearing updates, make sure both sides agree on:0..18000That keeps your UART packets simple and reliable.
If you want, I can now rewrite just these two Arduino functions in final production-ready form and match them to your current full sketch exactly.
@Athena. Thank you for your analysis. I have 3 requests.
1. I would like to perform Right Triangle equations to the right (as I am currently doing) and perform Right Triangle equations to the left. An equation to the right resulting in the OppositeDistance side proceeding to the right towards the reflector. An equation to the left resulting in the OppositeDistance side proceeding to the left towards the reflector. So, would an equation to the left be negative and an equation to the right be positive? If so, I need you to utilize atan2().
Main concern: use of
atan()instead ofatan2()If
oppositeDistanceis always positive and you only need a single-sided geometry angle,atan()is fine. But if you want a more robust bearing calculation,atan2()is usually better because it handles sign and quadrants properly. For your current design, though,atan()is acceptable if the geometry is always positive and one-directional. 2.Keep:
desiredBearing100 = (deg + 90.0) * 100.0bearingDiffTarget = actual - desiredImprove:
pathDistanceRemaining <= 0desiredBearing100to0..18000updateBearingDiff()3. One more small ARC/Arduino consistency note Because ARC is sending
actualBearing100initially and later sending computed actual bearing updates, make sure both sides agree on:0..18000That keeps your UART packets simple and reliable.
Thank you. Jack
Yes - for a left/right triangle solution,
atan2()is the right choice.Short answer
atan2(opposite, adjacent)return:So yes, if you want the triangle to resolve in both directions, you should move from
atan()toatan2().How that applies to your case
If your geometry is:
pathDistanceRemainingthen you can define:
oppositeDistance > 0= reflector/right target is to the rightoppositeDistance < 0= reflector/left target is to the leftThen:
This produces:
What happens to your bearing math
If you keep your current convention of storing bearings as
0..18000with a center offset, then you can still do:That means:
-45.0becomes45000.0becomes9000+45.0becomes13500So your existing
+90offset model still works.Recommended formula
For your left/right navigation, I’d recommend:
Important design decision
You need to decide whether left/right offset should be signed at the source or derived later.
Option A - signed
oppositeDistanceatan2()Option B - unsigned distance plus a direction flag
oppositeDistancestays positiveturnDir = -1 or +1If your current data already includes a left/right direction, this may be the cleaner approach.
About
bearingDiffTarget = actual - desiredYes, keep that.
That means:
actualis greater thandesired, the robot is effectively rotated one wayactualis less thandesired, it is rotated the other wayThat sign convention is fine as long as your correction logic uses it consistently.
About the
updateBearingDiff()smoothingYes, keep the guard and the overshoot protection. That part is good practice.
I’d recommend this version:
About ARC / Arduino consistency
Yes, your summary is exactly right:
That makes the protocol much cleaner and easier to debug.
My recommendation for your geometry model
Use this logic:
oppositeDistanceoppositeDistanceatan2()0..18000bearing scale by adding90.0That gives you a single math model that supports both directions naturally.
If you want, I can now rewrite your full
computeDesiredBearing()andupdateBearingDiff()functions in final Arduino form usingatan2()and your left/right signed-distance approach.@Athena. Please rewrite your full
computeDesiredBearing()andupdateBearingDiff()functions in final Arduino form usingatan2()and your left/right signed-distance approach. Thank you. JackSorry, 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:
@Athena. Thank you for your help. Very Informative. Jack