Hi @Athena,
I'm running an ARC project on an EZBv4 that manages an Arduino Mega (motor control) and a Teensy 4.1 (servo/sensor control). The Mega and Teensy each run firmware (posted below) and are paired with corresponding UART Robot Skills in ARC: MEGA_MGR_v7 (for the Mega) and TEENSY_MGR_v2 (for the Teensy). I have included the firmware and skill code below.
High-level setup / connection model:
- ARC (on the PC) connects to an EZ-B v4 (EZBv4).
- The Arduino Mega and Teensy 4.1 are connected to the EZBv4 UART(s).
- ARC accesses each controller via separate UART Robot Skills (MEGA_MGR_v7 and TEENSY_MGR_v2).
- MEGA_MGR_v7 parses/constructs a binary packet protocol (header 0xA5). MEGA_FIRMWARE_v6 expects RSP_TOTAL_STEPS (0x11) to be sent on stops.
- TEENSY firmware uses an ASCII packet format like "$PAYLOAD*CHK\n". TEENSY_MGR_v2 parses lines and currently prints REPL results; TEENSY_MGR_v2 contains placeholders where ARC UART ControlCommand calls should be used to read/write to the EZB UART.
Summary - Mega side
- Firmware: MEGA_FIRMWARE_v6 (Arduino Mega)
- Robot Skill: MEGA_MGR_v7 (UART robot skill in ARC)
- Responsibilities of MEGA_FIRMWARE_v6:
- Receive WayPoint data.
- Receive Pivot data.
- Receive STOP command.
- Receive actual bearing updates.
- Send STOP command.
- Send obstacle-detection scan requests at required intervals (step counts).
- Send STEPS whenever a STOP command is sent or received.
- Send periodic updates of desired bearing when desired bearing changes by 1 degree.
- Count STEPS and STOP when Pivot Data limits are reached or combinedStepsDesired is reached.
- Count STEPS for obstacle-detection scan intervals and send the scan request.
- Control motors based on the differential between desired and actual bearing.
- Compute desired bearings if WayPoint data indicates bearing_alignment = 1 (offset reflector position).
Problem #1 (Mega) When the system stops, no STEPS (TOTAL_STEPS) are printed to debug (item #7 above). Can you troubleshoot why reportStepsOnlyOnStop() / RSP_TOTAL_STEPS are not being observed by MEGA_MGR_v7 in ARC?
Summary - Teensy side
- Firmware: TEENSY_FIRMWARE_v4 (Teensy 4.1) including PacketHandler
- Robot Skill: TEENSY_MGR_v2 (UART robot skill in ARC)
- Responsibilities of TEENSY_FIRMWARE_v4:
- Sweep servo left/right searching for a reflector.
- Detect reflector-search runaway and send a STOP command to TEENSY_MGR_v2.
- Compute actual bearing updates and send them to TEENSY_MGR_v2.
Problem #2 (Teensy -> Mega) As actual bearing updates are generated by the Teensy firmware + TEENSY_MGR_v2 combo, they need to be forwarded to the MEGA firmware + MEGA_MGR_v7 combo. That data flow is not working reliably. Can you troubleshoot why bearing updates sent from the Teensy / TEENSY_MGR are not reaching the Mega / MEGA_MGR?
Notes / additional context for troubleshooting
- Both MEGA_MGR_v7 and TEENSY_MGR_v2 are implemented as UART Robot Skills in ARC and parse/build binary or text packets respectively.
- MEGA_FIRMWARE_v6 uses a binary packet protocol (header 0xA5) and expects RSP_TOTAL_STEPS (0x11) to be sent on stop events.
- TEENSY firmware uses ASCII packet format like "$PAYLOAD*CHK\n". TEENSY_MGR_v2 parses lines and prints REPL results; TEENSY_MGR_v2 contains placeholders where ARC UART ControlCommand calls should write/read to/from the EZB UART.
- The EZBv4 is the EZ-B controller that ARC connects to; the Mega and Teensy communicate through the EZBv4 UART(s). The setup implies the Mega and Teensy are connected through the EZBv4 serial interface(s) that ARC accesses via UART Robot Skills.
I have posted the applicable code below. Thank you - Jack
MEGA_FIRMWARE_v6
#include <AccelStepper.h>
static const uint32_t BAUD = 115200;
static const uint8_t UART_HEADER = 0xA5;
#define LINK_SERIAL Serial3
#define DBG_SERIAL Serial
// ======================================================
// Commands from ARC / MEGA_MGR
// ======================================================
enum CommandId : uint8_t {
CMD_STOP_MOTORS = 0x01,
CMD_ACTUAL_BEARING = 0x03,
CMD_RIGHT_PIVOT = 0x04,
CMD_LEFT_PIVOT = 0x05,
CMD_WAYPOINT_DATA = 0x06,
CMD_RESUME = 0x07,
CMD_PAUSE = 0x08
};
// ======================================================
// Responses to ARC / MEGA_MGR
// ======================================================
enum ResponseId : uint8_t {
RSP_NAV_ACK = 0x10,
RSP_TOTAL_STEPS = 0x11,
RSP_DESIRED_BEARING = 0x12,
RSP_OBS_SCAN_REQUEST = 0x13,
RSP_READY = 0x14,
RSP_STATUS = 0x15,
RSP_ACK = 0x16,
RSP_NAK = 0x17
};
// ======================================================
// Stepper 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);
// ======================================================
// Motion state
// ======================================================
enum MotionMode {
MODE_IDLE = 0,
MODE_PIVOT = 1,
MODE_TRACK = 2,
MODE_PAUSED = 3,
MODE_COMPLETE = 4
};
MotionMode mode = MODE_IDLE;
MotionMode resumeMode = MODE_IDLE;
bool motorsEnabled = false;
bool systemReady = false;
// ======================================================
// Waypoint / leg data
// ======================================================
uint32_t pathTotalDistance = 0;
uint32_t combinedStepsDesired = 0;
uint32_t oppositeDistance = 0;
uint8_t bearingAlignment = 0;
uint16_t desiredBearing100 = 9000;
uint16_t actualBearing100 = 9000;
uint8_t trackDir = 0;
uint32_t scanIntervalSteps = 3056;
// ======================================================
// Runtime
// ======================================================
uint32_t totalStepsTaken = 0;
uint32_t lastReportedDesiredBearing = 0xFFFF;
uint32_t lastReportedStepsOnStop = 0;
// Leg tracking
uint32_t legStartSteps = 0;
uint32_t nextScanAtSteps = 0;
bool legLoaded = false;
bool scanPending = false;
bool stopJustOccurred = false;
// ======================================================
// Motion control
// ======================================================
int wheelSpeedBase = 1600;
int leftTargetSpeed = 0;
int rightTargetSpeed = 0;
int leftCurrentSpeed = 0;
int rightCurrentSpeed = 0;
int speedRampStep = 25;
// ======================================================
// RX parser
// ======================================================
uint8_t rxState = 0;
uint8_t rxCmd = 0;
uint8_t rxLen = 0;
uint8_t rxIndex = 0;
uint8_t rxChecksum = 0;
uint8_t rxPayload[64];
// ======================================================
// Helpers
// ======================================================
void debugPrint(const String &s) {
DBG_SERIAL.println(s);
}
void sendBinaryPacket(uint8_t cmd, const uint8_t *payload, uint8_t len) {
uint8_t cs = cmd ^ len;
LINK_SERIAL.write(UART_HEADER);
LINK_SERIAL.write(cmd);
LINK_SERIAL.write(len);
for (uint8_t i = 0; i < len; i++) {
LINK_SERIAL.write(payload[i]);
cs ^= payload[i];
}
LINK_SERIAL.write(cs);
}
void sendAck(const String &msg) {
uint8_t payload[24];
uint8_t len = msg.length();
if (len > 24) len = 24;
for (uint8_t i = 0; i < len; i++) payload[i] = (uint8_t)msg[i];
sendBinaryPacket(RSP_ACK, payload, len);
}
void sendNak(const String &msg) {
uint8_t payload[24];
uint8_t len = msg.length();
if (len > 24) len = 24;
for (uint8_t i = 0; i < len; i++) payload[i] = (uint8_t)msg[i];
sendBinaryPacket(RSP_NAK, payload, len);
}
uint16_t readU16LE(const uint8_t *b) {
return (uint16_t)b[0] | ((uint16_t)b[1] << 8);
}
uint32_t readU32LE(const uint8_t *b) {
return (uint32_t)b[0]
| ((uint32_t)b[1] << 8)
| ((uint32_t)b[2] << 16)
| ((uint32_t)b[3] << 24);
}
void sendStatus() {
uint8_t payload[2];
payload[0] = motorsEnabled ? 1 : 0;
payload[1] = (uint8_t)mode;
sendBinaryPacket(RSP_STATUS, payload, 2);
}
void sendNavAck(const char *msg) {
sendBinaryPacket(RSP_NAV_ACK, (const uint8_t *)msg, strlen(msg));
}
void sendDesiredBearingIfChanged() {
if (desiredBearing100 != lastReportedDesiredBearing) {
uint8_t payload[2];
payload[0] = desiredBearing100 & 0xFF;
payload[1] = (desiredBearing100 >> 8) & 0xFF;
sendBinaryPacket(RSP_DESIRED_BEARING, payload, 2);
lastReportedDesiredBearing = desiredBearing100;
}
}
void reportStepsOnlyOnStop() {
uint8_t payload[4];
payload[0] = totalStepsTaken & 0xFF;
payload[1] = (totalStepsTaken >> 8) & 0xFF;
payload[2] = (totalStepsTaken >> 16) & 0xFF;
payload[3] = (totalStepsTaken >> 24) & 0xFF;
sendBinaryPacket(RSP_TOTAL_STEPS, payload, 4);
lastReportedStepsOnStop = totalStepsTaken;
}
void stopMotors() {
leftTargetSpeed = 0;
rightTargetSpeed = 0;
leftCurrentSpeed = 0;
rightCurrentSpeed = 0;
LeftFrontWheel.setSpeed(0);
LeftBackWheel.setSpeed(0);
RightFrontWheel.setSpeed(0);
RightBackWheel.setSpeed(0);
LeftFrontWheel.runSpeed();
LeftBackWheel.runSpeed();
RightFrontWheel.runSpeed();
RightBackWheel.runSpeed();
}
void applyMotorSpeeds() {
LeftFrontWheel.setSpeed(leftCurrentSpeed);
LeftBackWheel.setSpeed(leftCurrentSpeed);
RightFrontWheel.setSpeed(rightCurrentSpeed);
RightBackWheel.setSpeed(rightCurrentSpeed);
LeftFrontWheel.runSpeed();
LeftBackWheel.runSpeed();
RightFrontWheel.runSpeed();
RightBackWheel.runSpeed();
}
void rampTowardTargets() {
if (leftCurrentSpeed < leftTargetSpeed) {
leftCurrentSpeed += speedRampStep;
if (leftCurrentSpeed > leftTargetSpeed) leftCurrentSpeed = leftTargetSpeed;
} else if (leftCurrentSpeed > leftTargetSpeed) {
leftCurrentSpeed -= speedRampStep;
if (leftCurrentSpeed < leftTargetSpeed) leftCurrentSpeed = leftTargetSpeed;
}
if (rightCurrentSpeed < rightTargetSpeed) {
rightCurrentSpeed += speedRampStep;
if (rightCurrentSpeed > rightTargetSpeed) rightCurrentSpeed = rightTargetSpeed;
} else if (rightCurrentSpeed > rightTargetSpeed) {
rightCurrentSpeed -= speedRampStep;
if (rightCurrentSpeed < rightTargetSpeed) rightCurrentSpeed = rightTargetSpeed;
}
}
void setMotionMode(MotionMode newMode) {
if (mode == newMode) return;
mode = newMode;
sendStatus();
}
void startLeg() {
legStartSteps = totalStepsTaken;
nextScanAtSteps = scanIntervalSteps;
legLoaded = true;
scanPending = false;
stopJustOccurred = false;
motorsEnabled = true;
}
void requestObstacleScan() {
sendBinaryPacket(RSP_OBS_SCAN_REQUEST, nullptr, 0);
}
void handleStopEvent(const char *reason) {
debugPrint(String("STOP: ") + reason);
stopMotors();
motorsEnabled = false;
setMotionMode(MODE_IDLE);
reportStepsOnlyOnStop();
sendAck("STOP");
}
void handleWaypointPacket(const uint8_t *p, uint8_t len) {
if (len != 22) {
sendNak("WAYPOINT_LEN");
return;
}
pathTotalDistance = readU32LE(&p[0]);
combinedStepsDesired = readU32LE(&p[4]);
oppositeDistance = readU32LE(&p[8]);
bearingAlignment = p[12];
desiredBearing100 = readU16LE(&p[13]);
actualBearing100 = readU16LE(&p[15]);
trackDir = p[17];
scanIntervalSteps = readU32LE(&p[18]);
debugPrint("WAYPOINT received");
debugPrint("pathTotalDistance=" + String(pathTotalDistance));
debugPrint("combinedStepsDesired=" + String(combinedStepsDesired));
debugPrint("oppositeDistance=" + String(oppositeDistance));
debugPrint("bearingAlignment=" + String(bearingAlignment));
debugPrint("desiredBearing100=" + String(desiredBearing100));
debugPrint("actualBearing100=" + String(actualBearing100));
debugPrint("trackDir=" + String(trackDir));
debugPrint("scanIntervalSteps=" + String(scanIntervalSteps));
if (bearingAlignment == 1) {
// Offset reflector position logic can be refined later
// For now this just acknowledges that desired bearing can be adjusted locally.
}
sendDesiredBearingIfChanged();
sendNavAck("WAYPOINT");
setMotionMode(MODE_PIVOT);
startLeg();
}
void handleBinaryPacket(uint8_t cmd, const uint8_t *payload, uint8_t len) {
switch (cmd) {
case CMD_STOP_MOTORS:
handleStopEvent("CMD_STOP_MOTORS");
break;
case CMD_ACTUAL_BEARING:
if (len != 2) {
sendNak("ACTUAL_BEARING_LEN");
return;
}
actualBearing100 = readU16LE(payload);
sendAck("ACTUAL_BEARING");
break;
case CMD_RIGHT_PIVOT:
if (len != 2) {
sendNak("RIGHT_PIVOT_LEN");
return;
}
combinedStepsDesired = readU16LE(payload);
motorsEnabled = true;
setMotionMode(MODE_PIVOT);
sendAck("RIGHT_PIVOT");
break;
case CMD_LEFT_PIVOT:
if (len != 2) {
sendNak("LEFT_PIVOT_LEN");
return;
}
combinedStepsDesired = readU16LE(payload);
motorsEnabled = true;
setMotionMode(MODE_PIVOT);
sendAck("LEFT_PIVOT");
break;
case CMD_WAYPOINT_DATA:
handleWaypointPacket(payload, len);
break;
case CMD_RESUME:
motorsEnabled = true;
setMotionMode(resumeMode);
sendAck("RESUME");
break;
case CMD_PAUSE:
resumeMode = mode;
motorsEnabled = false;
setMotionMode(MODE_PAUSED);
stopMotors();
sendAck("PAUSE");
break;
default:
sendNak("UNKNOWN_CMD");
break;
}
}
void processIncoming() {
while (LINK_SERIAL.available() > 0) {
uint8_t b = (uint8_t)LINK_SERIAL.read();
switch (rxState) {
case 0:
if (b == UART_HEADER) rxState = 1;
break;
case 1:
rxCmd = b;
rxChecksum = rxCmd;
rxState = 2;
break;
case 2:
rxLen = b;
rxChecksum ^= rxLen;
rxIndex = 0;
if (rxLen > sizeof(rxPayload)) {
rxState = 0;
sendNak("LEN_OVF");
} else {
rxState = (rxLen == 0) ? 4 : 3;
}
break;
case 3:
rxPayload[rxIndex++] = b;
rxChecksum ^= b;
if (rxIndex >= rxLen) rxState = 4;
break;
case 4:
if (b == rxChecksum) {
handleBinaryPacket(rxCmd, rxPayload, rxLen);
} else {
sendNak("CS");
}
rxState = 0;
rxCmd = 0;
rxLen = 0;
rxIndex = 0;
rxChecksum = 0;
break;
}
}
}
void updateMotion() {
if (!motorsEnabled) {
stopMotors();
return;
}
uint32_t stepsInLeg = totalStepsTaken - legStartSteps;
if (mode == MODE_PIVOT) {
int error = (int)desiredBearing100 - (int)actualBearing100;
if (error > 25) {
leftTargetSpeed = wheelSpeedBase;
rightTargetSpeed = -wheelSpeedBase;
} else if (error < -25) {
leftTargetSpeed = -wheelSpeedBase;
rightTargetSpeed = wheelSpeedBase;
} else {
// Pivot complete -> move into track mode
leftTargetSpeed = 0;
rightTargetSpeed = 0;
setMotionMode(MODE_TRACK);
}
}
if (mode == MODE_TRACK) {
int error = (int)desiredBearing100 - (int)actualBearing100;
int steer = error / 50;
if (steer > 400) steer = 400;
if (steer < -400) steer = -400;
leftTargetSpeed = wheelSpeedBase + steer;
rightTargetSpeed = wheelSpeedBase - steer;
if (leftTargetSpeed > wheelSpeedBase + 400) leftTargetSpeed = wheelSpeedBase + 400;
if (rightTargetSpeed > wheelSpeedBase + 400) rightTargetSpeed = wheelSpeedBase + 400;
if (leftTargetSpeed < -wheelSpeedBase) leftTargetSpeed = -wheelSpeedBase;
if (rightTargetSpeed < -wheelSpeedBase) rightTargetSpeed = -wheelSpeedBase;
if (!scanPending && scanIntervalSteps > 0 && stepsInLeg >= nextScanAtSteps) {
scanPending = true;
requestObstacleScan();
nextScanAtSteps += scanIntervalSteps;
}
if (combinedStepsDesired > 0 && stepsInLeg >= combinedStepsDesired) {
handleStopEvent("LEG_COMPLETE");
return;
}
}
if (mode == MODE_PAUSED || mode == MODE_COMPLETE || mode == MODE_IDLE) {
leftTargetSpeed = 0;
rightTargetSpeed = 0;
}
rampTowardTargets();
applyMotorSpeeds();
totalStepsTaken = (uint32_t)(labs(LeftFrontWheel.currentPosition()) + labs(RightFrontWheel.currentPosition()));
}
void setup() {
DBG_SERIAL.begin(BAUD);
LINK_SERIAL.begin(BAUD);
LeftFrontWheel.setMaxSpeed(wheelSpeedBase * 2);
LeftBackWheel.setMaxSpeed(wheelSpeedBase * 2);
RightFrontWheel.setMaxSpeed(wheelSpeedBase * 2);
RightBackWheel.setMaxSpeed(wheelSpeedBase * 2);
debugPrint("MEGA_FIRMWARE_v6 started");
uint8_t payload[] = { 'R', 'E', 'A', 'D', 'Y' };
sendBinaryPacket(RSP_READY, payload, 5);
sendStatus();
}
void loop() {
processIncoming();
// Only update bearing announcement when it changes by 1 degree or more.
sendDesiredBearingIfChanged();
updateMotion();
delay(1);
}
MEGA_MGR_v7
var UART_INDEX = 0;
var UART_BAUD = 115200;
var UART_HEADER = 0xA5;
// Commands from ARC
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;
var CMD_RESUME = 0x07;
var CMD_PAUSE = 0x08;
// Responses from Mega
var RSP_NAV_ACK = 0x10;
var RSP_TOTAL_STEPS = 0x11;
var RSP_DESIRED_BEARING = 0x12;
var RSP_OBS_SCAN_REQUEST = 0x13;
var RSP_READY = 0x14;
var RSP_STATUS = 0x15;
var RSP_ACK = 0x16;
var RSP_NAK = 0x17;
var rxState = 0;
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 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 sendPacket(cmd, payload) {
var checksum = cmd ^ payload.length;
UART.hardwareUartWrite(UART_INDEX, [UART_HEADER]);
UART.hardwareUartWrite(UART_INDEX, [cmd]);
UART.hardwareUartWrite(UART_INDEX, [payload.length]);
if (payload.length > 0) {
UART.hardwareUartWrite(UART_INDEX, payload);
for (var i = 0; i < payload.length; i++) {
checksum ^= (payload[i] & 0xFF);
}
}
UART.hardwareUartWrite(UART_INDEX, [checksum & 0xFF]);
}
function sendStopMotors() {
sendPacket(CMD_STOP_MOTORS, []);
print("TX: STOP");
}
function sendActualBearing() {
var bearing100 = safeInt(getVar("$actualBearing100", 9000), 9000);
bearing100 = Math.max(0, Math.min(18000, bearing100));
sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
print("TX: ACTUAL BEARING = " + bearing100);
}
function sendWaypointData() {
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;
var trackDir = safeInt(getVar("$trackDir", 0), 0) ? 1 : 0;
var desiredBearing100 = safeInt(getVar("$desiredBearing100", 9000), 9000);
var actualBearing100 = safeInt(getVar("$actualBearing100", 9000), 9000);
var scanIntervalSteps = safeInt(getVar("$scanIntervalSteps", 3056), 3056) >>> 0;
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);
payload = payload.concat(u32ToBytes(scanIntervalSteps));
if (payload.length !== 22) {
print("Waypoint payload length error: " + payload.length);
return;
}
setVar("$legComplete", 0);
setVar("$navAckReceived", 0);
sendPacket(CMD_WAYPOINT_DATA, payload);
print("TX: WAYPOINT DATA");
}
function sendPivot(cmd, name) {
var steps = safeInt(getVar("$pivotSteps", 0), 0) >>> 0;
sendPacket(cmd, u16ToBytes(steps));
print("TX: " + name + " steps=" + steps);
}
function sendPause() {
sendPacket(CMD_PAUSE, []);
print("TX: PAUSE");
}
function sendResume() {
sendPacket(CMD_RESUME, []);
print("TX: RESUME");
}
function handleRxPacket(cmd, payload) {
if (cmd == RSP_READY) {
print("RX: READY");
setVar("$megaReady", 1);
return;
}
if (cmd == RSP_ACK) {
print("RX: ACK");
return;
}
if (cmd == RSP_NAK) {
print("RX: NAK");
return;
}
if (cmd == RSP_NAV_ACK) {
print("RX: NAV ACK");
setVar("$navAckReceived", 1);
return;
}
if (cmd == RSP_STATUS) {
print("RX: STATUS");
return;
}
if (cmd == RSP_DESIRED_BEARING) {
if (payload.length == 2) {
var bearing100 = bytesToU16LE(payload[0], payload[1]);
setVar("$desiredBearing100", bearing100);
print("RX: DESIRED BEARING x100 = " + bearing100);
}
return;
}
if (cmd == RSP_OBS_SCAN_REQUEST) {
setVar("$obsScanRequested", 1);
print("RX: OBSTACLE SCAN REQUEST");
return;
}
// Ignore step totals unless you explicitly want to display them later.
}
function processIncomingBytes() {
var available = UART.hardwareUartAvailable(UART_INDEX);
while (available > 0) {
var b = UART.hardwareUartRead(UART_INDEX, 1)[0] & 0xFF;
available--;
if (rxState == 0) {
if (b == UART_HEADER) rxState = 1;
continue;
}
if (rxState == 1) {
rxCmd = b;
rxChecksum = rxCmd;
rxState = 2;
continue;
}
if (rxState == 2) {
rxLen = b;
rxChecksum ^= rxLen;
rxPayload = [];
rxIndex = 0;
rxState = (rxLen == 0) ? 4 : 3;
continue;
}
if (rxState == 3) {
rxPayload.push(b);
rxChecksum ^= b;
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("MEGA_MGR_v7 started");
UART.initHardwareUart(UART_INDEX, UART_BAUD);
while (true) {
if (getVar("$sendWaypointNow", 0) == 1) {
sendWaypointData();
setVar("$sendWaypointNow", 0);
}
if (getVar("$sendStopNow", 0) == 1) {
sendStopMotors();
setVar("$sendStopNow", 0);
}
if (getVar("$sendPauseNow", 0) == 1) {
sendPause();
setVar("$sendPauseNow", 0);
}
if (getVar("$sendResumeNow", 0) == 1) {
sendResume();
setVar("$sendResumeNow", 0);
}
if (getVar("$sendActualBearingNow", 0) == 1) {
sendActualBearing();
setVar("$sendActualBearingNow", 0);
}
if (getVar("$sendRightPivotNow", 0) == 1) {
sendPivot(CMD_RIGHT_PIVOT, "RIGHT PIVOT");
setVar("$sendRightPivotNow", 0);
}
if (getVar("$sendLeftPivotNow", 0) == 1) {
sendPivot(CMD_LEFT_PIVOT, "LEFT PIVOT");
setVar("$sendLeftPivotNow", 0);
}
processIncomingBytes();
sleep(20);
}
TEENSY_FIRMWARE_v4 and PacketHandler
//PacketHandler.h
#pragma once
#include <Arduino.h>
struct PacketResult {
bool valid;
String payload;
};
uint8_t simpleChecksum(const String& payload);
String toHex2(uint8_t value);
PacketResult parsePacket(const String& packet);
String buildPacket(const String& payload);
PacketHandler.cpp
#include "PacketHandler.h"
uint8_t simpleChecksum(const String& payload) {
uint8_t sum = 0;
for (size_t i = 0; i < payload.length(); i++) {
sum ^= (uint8_t)payload.charAt(i);
}
return sum;
}
String toHex2(uint8_t value) {
const char hex[] = "0123456789ABCDEF";
String out;
out += hex[(value >> 4) & 0x0F];
out += hex[value & 0x0F];
return out;
}
PacketResult parsePacket(const String& packet) {
PacketResult result;
result.valid = false;
result.payload = "";
if (packet.length() < 5) return result;
if (packet.charAt(0) != '$') return result;
int star = packet.indexOf('*');
if (star < 0) return result;
if (star + 2 >= packet.length()) return result;
String payload = packet.substring(1, star);
String checksumText = packet.substring(star + 1, star + 3);
uint8_t received = (uint8_t)strtol(checksumText.c_str(), nullptr, 16);
uint8_t computed = simpleChecksum(payload);
if (received != computed) return result;
result.valid = true;
result.payload = payload;
return result;
}
String buildPacket(const String& payload) {
uint8_t chk = simpleChecksum(payload);
return "$" + payload + "*" + toHex2(chk) + "\n";
}
IR_TRACKER_v4.h
#pragma once
#include <Arduino.h>
#include <Servo.h>
enum TrackerState {
NEG_SEARCH_INIT,
NEG_SEARCH_FINAL,
POS_SEARCH_INIT,
POS_SEARCH_FINAL,
TRACKER_STOPPED
};
static const uint8_t SERVO_PIN = 29;
static const uint8_t IR_PIN = A14;
static const int SERVO_MIN_ANGLE = 5;
static const int SERVO_MAX_ANGLE = 175;
static const int SERVO_MIN_PULSE = 500;
static const int SERVO_MAX_PULSE = 2500;
static const int DESIRED_BEARING = 90;
static const int STEP_DELTA = 1;
static const int RUNAWAY_MARGIN = 20;
static const int RESET_DELAY_MS = 20;
static const int SETTLE_DELAY_MS = 30;
static const int IR_THRESHOLD = 15;
static const int IR_SMOOTH_SAMPLES = 8;
static const int ADC_MAX_RAW = 4095;
static const long NEG_OBS_MAX = 2000;
static const long POS_OBS_MAX = 2000;
static const unsigned long EZB_BAUD = 115200;
extern servo trackingServo;
extern TrackerState state;
extern int desiredBearing;
extern int roundedBearing;
extern int actualBearing;
extern int bearing;
extern int negInit;
extern int negFinal;
extern int posInit;
extern int posFinal;
extern long negObsTot;
extern long posObsTot;
extern int bearingCount;
extern int runawayCount;
extern int negAbortLimit;
extern int posAbortLimit;
extern bool stopRequested;
extern bool startRequested;
void setupTracker();
void loopTracker();
void beginNegSweep();
void beginPosSweep();
void doNegSearchInit();
void doNegSearchFinal();
void doPosSearchInit();
void doPosSearchFinal();
void stopNow(const char* reason);
void processTrackerState();
int normalizeIRTo255(int raw);
int readIRSmooth();
int clampServoAngle(int angle);
void setServoBearing(int angle);
int readServo();
void publishBearing(int candidate);
void updateBearingFromInitEdges();
void updateBearingFromFinalEdges();
void handleRunaway();
void pollSerial();
void handleSerialCommand(const String& cmd);
void sendToEZB(const String& msg);
void sendBearingUpdate();
void sendBearingCount();
void sendStatus();
void sendAck(const String& cmd);
void sendNak(const String& reason);
IR_TRACKER_v4.cpp
#include "IR_TRACKER_v4.h"
#include "PacketHandler.h"
Servo trackingServo;
TrackerState state = NEG_SEARCH_INIT;
int desiredBearing = DESIRED_BEARING;
int roundedBearing = DESIRED_BEARING;
int actualBearing = DESIRED_BEARING;
int bearing = DESIRED_BEARING;
int negInit = -1;
int negFinal = -1;
int posInit = -1;
int posFinal = -1;
long negObsTot = 0;
long posObsTot = 0;
int bearingCount = 0;
int runawayCount = 0;
int negAbortLimit = 0;
int posAbortLimit = 0;
bool stopRequested = false;
bool startRequested = false;
static int irSamples[IR_SMOOTH_SAMPLES];
static int irSampleIndex = 0;
static bool irInitialized = false;
static unsigned long lastDebugMs = 0;
static int lastActualBearing = -1;
static int lastPublishedBearing = -1;
static int prevLastPublishedBearing = -1;
static String serialPacket;
static bool inPacket = false;
static unsigned long packetStartMs = 0;
static bool teensyReady = false;
static int clampAngle(int angle) {
return constrain(angle, SERVO_MIN_ANGLE, SERVO_MAX_ANGLE);
}
int clampServoAngle(int angle) {
return clampAngle(angle);
}
void setServoBearing(int angle) {
bearing = clampAngle(angle);
trackingServo.write(bearing);
}
int readServo() {
return bearing;
}
int normalizeIRTo255(int raw) {
raw = constrain(raw, 0, ADC_MAX_RAW);
return map(raw, 0, ADC_MAX_RAW, 0, 255);
}
int readIRSmooth() {
int raw = analogRead(IR_PIN);
if (!irInitialized) {
for (int i = 0; i < IR_SMOOTH_SAMPLES; i++) {
irSamples[i] = raw;
}
irInitialized = true;
} else {
irSamples[irSampleIndex] = raw;
irSampleIndex = (irSampleIndex + 1) % IR_SMOOTH_SAMPLES;
}
long sum = 0;
for (int i = 0; i < IR_SMOOTH_SAMPLES; i++) {
sum += irSamples[i];
}
return (int)(sum / IR_SMOOTH_SAMPLES);
}
void sendToEZB(const String& msg) {
Serial8.print(buildPacket(msg));
}
void sendAck(const String& cmd) {
sendToEZB("ACK," + cmd);
}
void sendNak(const String& reason) {
sendToEZB("NAK," + reason);
}
void sendBearingUpdate() {
sendToEZB("BEARING," + String(actualBearing));
}
void sendBearingCount() {
sendToEZB("COUNT," + String(bearingCount));
}
void sendStatus() {
String msg = "STATUS,STATE=" + String((int)state);
msg += ",BEARING=" + String(bearing);
msg += ",ACTUAL=" + String(actualBearing);
msg += ",COUNT=" + String(bearingCount);
msg += ",RUNAWAY=" + String(runawayCount);
msg += ",READY=" + String(teensyReady ? 1 : 0);
sendToEZB(msg);
}
void publishBearing(int candidate) {
roundedBearing = candidate;
if (roundedBearing == lastActualBearing) {
if (roundedBearing != lastPublishedBearing && roundedBearing != prevLastPublishedBearing) {
prevLastPublishedBearing = lastPublishedBearing;
lastPublishedBearing = roundedBearing;
actualBearing = roundedBearing;
sendBearingUpdate();
}
} else {
lastActualBearing = roundedBearing;
}
}
void updateBearingFromInitEdges() {
if (negInit >= 0 && posInit >= 0) {
int candidate = (negInit + posInit) / 2;
bearingCount++;
publishBearing(candidate);
}
}
void updateBearingFromFinalEdges() {
if (negFinal >= 0 && posFinal >= 0) {
int candidate = (negFinal + posFinal) / 2;
bearingCount++;
publishBearing(candidate);
}
}
void stopNow(const char* reason) {
Serial.print("STOP: ");
Serial.println(reason);
setServoBearing(roundedBearing);
delay(RESET_DELAY_MS);
sendToEZB("STOP");
sendBearingCount();
stopRequested = true;
state = TRACKER_STOPPED;
}
void beginNegSweep() {
delay(SETTLE_DELAY_MS);
setServoBearing(roundedBearing);
delay(RESET_DELAY_MS);
if (negObsTot > NEG_OBS_MAX) {
stopNow("NEG OBS total exceeded max");
return;
}
if (posObsTot > POS_OBS_MAX) {
stopNow("POS OBS total exceeded max");
return;
}
negObsTot = 0;
posObsTot = 0;
negInit = -1;
negFinal = -1;
negAbortLimit = roundedBearing - RUNAWAY_MARGIN;
state = NEG_SEARCH_INIT;
}
void beginPosSweep() {
delay(SETTLE_DELAY_MS);
setServoBearing(roundedBearing);
delay(RESET_DELAY_MS);
posInit = -1;
posFinal = -1;
posAbortLimit = roundedBearing + RUNAWAY_MARGIN;
state = POS_SEARCH_INIT;
}
void handleRunaway() {
runawayCount++;
delay(SETTLE_DELAY_MS);
setServoBearing(roundedBearing);
delay(RESET_DELAY_MS);
if (runawayCount >= 3) {
stopNow("Runaway limit reached");
return;
}
if (state == NEG_SEARCH_INIT) {
beginPosSweep();
} else {
beginNegSweep();
}
}
void doNegSearchInit() {
int current = readServo();
int irNorm = normalizeIRTo255(readIRSmooth());
bool reflectorDetected = (irNorm >= IR_THRESHOLD);
if (current < negAbortLimit) {
handleRunaway();
return;
}
if (reflectorDetected) {
negInit = current;
updateBearingFromInitEdges();
state = NEG_SEARCH_FINAL;
} else {
int next = current - STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void doNegSearchFinal() {
int current = readServo();
int irNorm = normalizeIRTo255(readIRSmooth());
bool reflectorDetected = (irNorm >= IR_THRESHOLD);
if (current < negAbortLimit) {
handleRunaway();
return;
}
if (reflectorDetected) {
negFinal = current;
updateBearingFromFinalEdges();
state = POS_SEARCH_INIT;
} else {
int next = current - STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void doPosSearchInit() {
int current = readServo();
int irNorm = normalizeIRTo255(readIRSmooth());
bool reflectorDetected = (irNorm >= IR_THRESHOLD);
if (current > posAbortLimit) {
handleRunaway();
return;
}
if (reflectorDetected) {
posInit = current;
updateBearingFromInitEdges();
state = POS_SEARCH_FINAL;
} else {
int next = current + STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void doPosSearchFinal() {
int current = readServo();
int irNorm = normalizeIRTo255(readIRSmooth());
bool reflectorDetected = (irNorm >= IR_THRESHOLD);
if (current > posAbortLimit) {
handleRunaway();
return;
}
if (reflectorDetected) {
posFinal = current;
state = NEG_SEARCH_INIT;
} else {
int next = current + STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void handleSerialCommand(const String& cmd) {
String s = cmd;
s.trim();
s.toUpperCase();
if (s == "HELLO") {
teensyReady = true;
sendToEZB("READY,IR_TRACKERv4.2.1");
sendAck("HELLO");
return;
}
if (s == "START") {
startRequested = true;
stopRequested = false;
sendAck("START");
return;
}
if (s == "STOP") {
stopNow("Serial STOP");
sendAck("STOP");
return;
}
if (s == "STATUS?") {
sendStatus();
sendAck("STATUS?");
return;
}
if (s.startsWith("BEARING,")) {
int value = s.substring(8).toInt();
roundedBearing = clampServoAngle(value);
setServoBearing(roundedBearing);
sendAck("BEARING");
return;
}
if (s.startsWith("COUNT,")) {
int value = s.substring(6).toInt();
bearingCount = value;
sendAck("COUNT");
return;
}
sendNak("UNKNOWN_CMD");
}
void pollSerial() {
while (Serial8.available() > 0) {
char c = (char)Serial8.read();
if (!inPacket) {
if (c == '$') {
inPacket = true;
serialPacket = "$";
packetStartMs = millis();
}
continue;
}
if (millis() - packetStartMs > 250) {
inPacket = false;
serialPacket = "";
sendNak("TIMEOUT");
continue;
}
serialPacket += c;
if (serialPacket.length() > 128) {
inPacket = false;
serialPacket = "";
sendNak("OVERFLOW");
continue;
}
if (c == '\n') {
PacketResult result = parsePacket(serialPacket);
if (result.valid) {
handleSerialCommand(result.payload);
} else {
sendNak("BAD_CRC");
}
serialPacket = "";
inPacket = false;
}
}
}
void processTrackerState() {
if (stopRequested || !startRequested) return;
switch (state) {
case NEG_SEARCH_INIT: doNegSearchInit(); break;
case NEG_SEARCH_FINAL: doNegSearchFinal(); break;
case POS_SEARCH_INIT: doPosSearchInit(); break;
case POS_SEARCH_FINAL: doPosSearchFinal(); break;
case TRACKER_STOPPED:
default:
return;
}
if (millis() - lastDebugMs >= 1000) {
lastDebugMs = millis();
Serial.print("state=");
Serial.print((int)state);
Serial.print(" bearing=");
Serial.print(bearing);
Serial.print(" actualBearing=");
Serial.print(actualBearing);
Serial.print(" count=");
Serial.print(bearingCount);
Serial.print(" ready=");
Serial.println(teensyReady ? 1 : 0);
}
}
void setupTracker() {
Serial.begin(EZB_BAUD);
Serial8.begin(EZB_BAUD);
delay(1000);
analogReadResolution(12);
pinMode(IR_PIN, INPUT);
trackingServo.attach(SERVO_PIN, SERVO_MIN_PULSE, SERVO_MAX_PULSE);
actualBearing = DESIRED_BEARING;
roundedBearing = DESIRED_BEARING;
bearing = DESIRED_BEARING;
setServoBearing(roundedBearing);
startRequested = false;
stopRequested = false;
teensyReady = false;
sendToEZB("READY,IR_TRACKERv4.2.1");
Serial.println("IR_TRACKERv4.2.1 READY");
}
void loopTracker() {
pollSerial();
processTrackerState();
}
TEENSY_MGR_v2
var running = true;
function checksumXor(payload) {
var sum = 0;
for (var i = 0; i < payload.length; i++) {
sum = sum ^ payload.charCodeAt(i);
}
return sum & 0xFF;
}
function toHex2(value) {
var hex = "0123456789ABCDEF";
return hex.charAt((value >> 4) & 0x0F) + hex.charAt(value & 0x0F);
}
function buildPacket(payload) {
var chk = checksumXor(payload);
return "$" + payload + "*" + toHex2(chk) + "\n";
}
function sendPacket(payload) {
var packet = buildPacket(payload);
print("TX: " + packet.trim());
// Replace this with the actual ARC command you use to write to the EZ-B UART.
// If you are using a UART robot skill, send through its ControlCommand API.
//
// Example pattern only:
// controlCommand("UART", "WriteString", packet);
}
function handleLine(line) {
line = line.trim();
if (line.length == 0) return;
print("RX: " + line);
if (line.charAt(0) != '$') return;
var star = line.indexOf('*');
if (star < 0) return;
var payload = line.substring(1, star);
var parts = payload.split(",");
if (parts[0] == "READY") {
print("Teensy ready: " + payload);
} else if (parts[0] == "ACK") {
print("ACK for " + parts[1]);
} else if (parts[0] == "NAK") {
print("NAK reason: " + parts[1]);
} else if (parts[0] == "STATUS") {
print("Status: " + payload);
} else if (parts[0] == "BEARING") {
print("Bearing: " + parts[1]);
} else if (parts[0] == "COUNT") {
print("Count: " + parts[1]);
} else if (parts[0] == "STOP") {
print("Teensy stopped");
running = false;
}
}
function pollTeensy() {
// Replace this with the ARC read method for the UART robot skill or EZ-B serial device.
// Example pattern only:
// while (UART.available() > 0) {
// var line = UART.readLine();
// handleLine(line);
// }
}
sleep(1000);
sendPacket("HELLO");
sendPacket("STATUS?");
sendPacket("START");
while (running) {
pollTeensy();
sleep(50);
}

Thanks - there are a couple of very likely failure points in this architecture, and both problems point to ARC UART routing / packet framing / skill wiring, not just firmware logic.
First, the biggest issue I see
Your Teensy manager is not actually sending anything to the UART yet.
In
TEENSY_MGR_v2you have:That means:
That alone explains Problem #2.
Problem #1: Why
RSP_TOTAL_STEPSis not observed byMEGA_MGR_v7The most likely causes
1) The Mega firmware may be sending the packet, but
MEGA_MGR_v7is not reading it at the right timeYour ARC JavaScript loop does:
That part is fine, but the Mega firmware may only send
RSP_TOTAL_STEPSinsidehandleStopEvent(), and your posted firmware snippet does not include that function or the stop path itself.So the first thing to verify is:
handleStopEvent()actually callingreportStepsOnlyOnStop()?sendBinaryPacket(RSP_TOTAL_STEPS, ...)before the serial buffer gets reset or the task exits?2) Your ARC parser may be ignoring
RSP_TOTAL_STEPSIn
MEGA_MGR_v7:So even if the Mega sends
RSP_TOTAL_STEPS, your manager currently appears to discard it silently.That means:
If you want to verify it, add:
And add a helper:
3) The Mega stop packet may be malformed
Your binary packet format is:
That’s fine, but if
sendBinaryPacket()is called with stale or incomplete data, ARC will reject it.Your ARC code expects:
0xA5I would add debug prints on the Mega before sending:
This will confirm whether the Mega is even transmitting the packet.
Very important Mega-side concern
Your snippet suggests
reportStepsOnlyOnStop()is implemented, but I do not see a guaranteed call to it in the stop path you posted.You need to confirm stop flow such as:
If stop events can happen more than once, the stop guard also matters. Otherwise you may send one packet and immediately overwrite state or suppress it.
Problem #2: Teensy bearing updates not reaching the Mega
The main issue
Your Teensy manager currently only prints received lines and has placeholder comments where UART writes should happen.
So the forward path:
Teensy firmware TEENSY_MGR_v2 Mega firmware / MEGA_MGR_v7
is incomplete.
Also: the serial topology looks wrong conceptually
You said:
That means ARC has to explicitly:
ARC will not automatically bridge the two UART skills together.
So if
TEENSY_MGR_v2is receiving a packet, it must actively forward it to the Mega through a UART write command.Recommended fix path
Option A: Forward inside ARC JavaScript
When
TEENSY_MGR_v2receives:it should call a function that writes to the Mega UART skill.
If the skill exposes UART through ARC control commands, use that.
If not, create a custom robot skill to act as the bridge, which is exactly what ARC is good at:
Option B: Let ARC manage both controllers directly
This is often cleaner:
controlCommand()For example, when Teensy manager parses bearing:
Then
MEGA_MGR_v7would need a custom command handler.Two specific code issues I noticed in the posted snippets
1)
sendActualBearing()is missing fromMEGA_MGR_v7Your loop references:
But the function definition isn’t shown.
If this function is incomplete or broken, your bridge may never transmit bearing data to the Mega.
2) Your
TEENSY_MGR_v2packet example doesn’t match the Teensy firmware packet formatYour Teensy firmware uses:
But your
handleLine()andsendPacket()need to actually write to the UART and not just print.Also, in ARC, if you’re using a UART Robot Skill, the write method depends on the actual skill implementation. If you want, I can help you convert this into a clean ARC JavaScript bridge.
What I would do next
Step 1: Confirm the Mega is transmitting
On the Mega, add debug output right before sending
RSP_TOTAL_STEPS.Step 2: Confirm ARC is receiving it
In
MEGA_MGR_v7, temporarily log every packet:and specifically handle
RSP_TOTAL_STEPS.Step 3: Implement Teensy forwarding
Make
TEENSY_MGR_v2actually write to its UART, then on parsed bearing do:Step 4: Verify the ARC skill wiring
Make sure:
Helpful ARC references
Create a robot skill if you want a clean bridge between Teensy and Mega logic:
https://synthiam.com/Support/Create-Robot-Skill/Overview
EZ-B Connection skill for UART / controller connection behavior:
https://synthiam.com/Support/Skills/Communication/EZB-Connection?id=16041
Robot Skills messaging with ControlCommand:
https://synthiam.com/Support/ARC-Overview/robot-skills/robot-skills-messaging
My diagnosis in one sentence
RSP_TOTAL_STEPSis likely either not being emitted in the stop path or is being ignored byMEGA_MGR_v7If you want, I can help you do either of these next:
MEGA_MGR_v7so it logs and forwardsRSP_TOTAL_STEPSproperly, orTEENSY_MGR_v2into a working ARC JavaScript bridge that forwards bearing updates to the Mega.If you paste the missing
handleStopEvent()andsendActualBearing()functions, I can pinpoint the exact break.Hi @Athena. Thank you for your diagnosis. I think it is time to step back and take a fresh look at an earlier starting point when things were clearer to me. I have posted a script from a robot skill called IR _TRACKER_28.
Request:
Thank you. Jack
Absolutely, Jack - and yes, this is a better place to restart.
The script you posted is really a state machine for an IR reflector tracker, so the correct translation to Teensy 4.1 is Arduino C++, not ARC JavaScript. That will give you:
Important note before the translation
Your pasted JavaScript is incomplete / corrupted in a few places:
publishBearing(candidate);is referenced but not shownreadServo()andreadIR()are referenced but not shownvar reflectorDetected = (irValue ...if (irValue ...settleDelayMs;appears where you likely meantsleep(settleDelayMs);So I can translate the intended behavior into C++, but I have to make a few assumptions.
Teensy 4.1 C++ translation
This version assumes:
Serial8in a simple ASCII formatWhat this translation is doing
It preserves the core behavior of your ARC script:
READYSTOPCOUNTBEARING,<value>ACK/NAKImportant correction
Your original ARC script appears to use servo position as bearing. That works, but in C++ you should be clear whether:
bearingmeans servo angleactualBearingmeans computed reflector bearingroundedBearingmeans the current center pointThat separation matters because otherwise the tracker gets hard to debug.
Better ARC/Teensy architecture
Since you’re moving this to Teensy 4.1, the cleanest pattern is:
That is much better than trying to keep the full tracker logic split across ARC JavaScript and firmware.
A couple of questions so I can make this exact
What exact servo hardware are you using on the Teensy 4.1?
Standard PWM servo, or a smart servo?
Do you want the Teensy to talk to ARC using the same ASCII packet format you already started with, or should I convert this to a binary protocol?
What is the ultimate goal of the tracker?
Is it just to find a reflector and report bearing, or should it also directly drive the Mega navigation logic?
If you want, I can do the next step and give you a full Teensy 4.1 project split into
.h/.cppfiles that is ready to paste into Arduino IDE.@Athena. Thank you for the translation into Arduino C++. After reviewing the translation, I have some concerns:
function doNegSearchFinal()compare to Arduino C++:void doNegSearchFinal()function doPosSearchFinal()compare to ArduinoC++:void doPosSearchFinal()Request:
IR\_TRACKER\_v28into Arduino C++, paying particular attention to the above functions noted above? Thank you for your continued support. JackYes - and you’re right to call that out.
1) About the code changing after submission
What you’re seeing is usually one of these issues:
<,>,&, quotes, and apostrophes can be altered when pasted into rich text or forum editors.Best way to prevent it
For reliable code sharing in ARC threads:
part 1part 2part 3Practical recommendation
For translation work, the safest approach is:
.txtor.jsfileThat gives the best chance of fidelity.
2) You’re also correct about the translation mismatch
My previous Arduino C++ response did not preserve your JavaScript structure closely enough. The better way is to translate the logic directly and literally, keeping the same state-machine flow and function names as much as possible.
Since you asked for a proper translation of IR_TRACKER_v28, below is a much closer Arduino C++ version for Teensy 4.1.
Arduino C++ translation of
IR_TRACKER_v28This version keeps the same core functions and state logic:
updateBearingFromInitEdges()updateBearingFromFinalEdges()stopNow()beginNegSweep()beginPosSweep()handleRunaway()doNegSearchInit()doNegSearchFinal()doPosSearchInit()doPosSearchFinal()It assumes:
servoPort = d17maps to a PWM-capable pin you chooseirPort = ADC2maps to an analog input pin you chooseNotes on fidelity
This is intentionally much closer to your JavaScript structure than my prior translation.
What I preserved
What needed interpretation
Your JavaScript appears to have some missing or truncated parts, including:
publishBearing(candidate)not fully shownnegAbortLimit/posAbortLimitnot fully declared in the snippet you pastedSo I translated the intended logic, not just the visible symbols.
Best practice suggestion
For a project like this, the proven better approach is:
That avoids the kind of split-brain debugging you were running into before.
Next step
If you want, I can do one of these next:
Make this compile-ready for Teensy 4.1
Serial8ARC packet protocolConvert your full original IR_TRACKER_v28 into a cleaner
.h/.cppprojectTranslate it into a Teensy + ARC communication pair
BEARING,<value>If you want the most useful next step, I’d recommend the Teensy + ARC communication pair so the tracker can run on the Teensy and still integrate cleanly with ARC.