- I have reposted IR_TRACKERv3 below.
#include "IR_TRACKERv3.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 lastActualBearing = -1;
int lastPublishedBearing = -1;
int prevLastPublishedBearing = -1;
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 String serialLine;
static unsigned long lastStepMs = 0;
static unsigned long lastDebugMs = 0;
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(msg);
Serial8.print('\n');
Serial.print("TX->EZB: ");
Serial.println(msg);
}
void sendBearingUpdate() {
sendToEZB("BEARING," + String(actualBearing));
}
void sendBearingCount() {
sendToEZB("COUNT," + String(bearingCount));
}
void publishBearing(int candidate) {
roundedBearing = round(candidate);
if (roundedBearing == lastActualBearing) {
if (roundedBearing != lastPublishedBearing) {
if (roundedBearing != prevLastPublishedBearing) {
prevLastPublishedBearing = lastPublishedBearing;
lastPublishedBearing = roundedBearing;
actualBearing = roundedBearing;
Serial.print("actualBearing: ");
Serial.println(actualBearing);
sendBearingUpdate();
}
}
} else {
lastActualBearing = roundedBearing;
}
}
void updateBearingFromInitEdges() {
int candidate = -1;
if (negInit >= 0 && posInit >= 0) {
candidate = (negInit + posInit) / 2;
}
if (candidate >= 0) {
bearingCount++;
publishBearing(candidate);
Serial.print("bearingCount: ");
Serial.println(bearingCount);
}
}
void updateBearingFromFinalEdges() {
int candidate = -1;
if (negFinal >= 0 && posFinal >= 0) {
candidate = (negFinal + posFinal) / 2;
}
if (candidate >= 0) {
bearingCount++;
publishBearing(candidate);
Serial.print("bearingCount: ");
Serial.println(bearingCount);
}
}
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) {
Serial.print("NEG OBS total exceeded max: ");
Serial.println(negObsTot);
stopNow("NEG OBS total exceeded max");
return;
}
if (posObsTot > POS_OBS_MAX) {
Serial.print("POS OBS total exceeded max: ");
Serial.println(posObsTot);
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++;
Serial.print("runawayCount: ");
Serial.println(runawayCount);
delay(SETTLE_DELAY_MS);
setServoBearing(roundedBearing);
delay(RESET_DELAY_MS);
if (state == NEG_SEARCH_INIT) {
beginPosSweep();
Serial.println("POS SWEEP");
} else {
beginNegSweep();
Serial.println("NEG SWEEP");
}
if (runawayCount >= 3) {
stopNow("Runaway limit reached");
return;
}
}
void doNegSearchInit() {
int current = readServo();
int irRaw = readIRSmooth();
int irNorm = normalizeIRTo255(irRaw);
bool reflectorDetected = (irNorm < IR_THRESHOLD);
if (current < negAbortLimit) {
handleRunaway();
return;
}
if (reflectorDetected) {
negInit = current;
runawayCount = 0;
updateBearingFromInitEdges();
state = NEG_SEARCH_FINAL;
} else {
int next = current - STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void doNegSearchFinal() {
int current = readServo();
int irRaw = readIRSmooth();
int irNorm = normalizeIRTo255(irRaw);
bool reflectorDetected = (irNorm < IR_THRESHOLD);
if (reflectorDetected) {
negFinal = current;
updateBearingFromFinalEdges();
beginPosSweep();
} else {
int next = current - STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void doPosSearchInit() {
int current = readServo();
int irRaw = readIRSmooth();
int irNorm = normalizeIRTo255(irRaw);
bool reflectorDetected = (irNorm < IR_THRESHOLD);
if (current > posAbortLimit) {
handleRunaway();
return;
}
if (reflectorDetected) {
posInit = current;
runawayCount = 0;
updateBearingFromInitEdges();
state = POS_SEARCH_FINAL;
} else {
int next = current + STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void doPosSearchFinal() {
int current = readServo();
int irRaw = readIRSmooth();
int irNorm = normalizeIRTo255(irRaw);
bool reflectorDetected = (irNorm < IR_THRESHOLD);
if (reflectorDetected) {
posFinal = current;
updateBearingFromFinalEdges();
beginNegSweep();
} else {
int next = current + STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void handleSerialCommand(const String& cmd) {
String trimmed = cmd;
trimmed.trim();
trimmed.toUpperCase();
if (trimmed == CMD_START) {
startRequested = true;
stopRequested = false;
sendToEZB("ACK_START");
return;
}
if (trimmed == CMD_STOP) {
stopRequested = true;
sendBearingCount();
sendToEZB("ACK_STOP");
return;
}
if (trimmed == "BEARING?") {
sendBearingUpdate();
return;
}
if (trimmed == "COUNT?") {
sendBearingCount();
return;
}
}
void pollSerial() {
while (Serial8.available() > 0) {
char c = (char)Serial8.read();
if (c == '\n' || c == '\r') {
if (serialLine.length() > 0) {
handleSerialCommand(serialLine);
serialLine = "";
}
} else {
serialLine += c;
}
}
}
void processTrackerState() {
if (stopRequested) {
return;
}
if (!startRequested) {
return;
}
unsigned long now = millis();
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 (now - lastDebugMs >= 1000) {
lastDebugMs = now;
Serial.print("state=");
Serial.print((int)state);
Serial.print(" bearing=");
Serial.print(bearing);
Serial.print(" actualBearing=");
Serial.print(actualBearing);
Serial.print(" negObsTot=");
Serial.print(negObsTot);
Serial.print(" posObsTot=");
Serial.println(posObsTot);
}
}
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);
beginNegSweep();
Serial.println("IR_TRACKERv3 READY");
Serial.println("Teensy Serial8 active on TX8=pin 35, RX8=pin 34");
Serial.println("Commands: START, STOP, BEARING?, COUNT?");
}
void loopTracker() {
pollSerial();
processTrackerState();
}
Can you make the following changes?
- UNDER function doNegSearchInit, please remove:
bool reflectorDetected = (irNorm < IR_THRESHOLD);
and add:
bool reflectorDetected = (irNorm <= IR_THRESHOLD);
- UNDER function doNegSearchFinal, please remove:
bool reflectorDetected = (irNorm < IR_THRESHOLD);
and add:
bool reflectorDetected = (irNorm > IR_THRESHOLD);
- UNDER function doPosSearchInit, please remove:
bool reflectorDetected = (irNorm < IR_THRESHOLD);
and add:
bool reflectorDetected = (irNorm <= IR_THRESHOLD);
- UNDER function doPosSearchFinal, please remove:
bool reflectorDetected = (irNorm < IR_THRESHOLD);
and add:
bool reflectorDetected = (irNorm > IR_THRESHOLD);
I am stuck getting IR_TRACKERv3 to compile and load on the Teensy 4.1. Your last instructions were:
- How to use the sketches
Yes - these are one Teensy project, not separate uploads.
You do this:
- Create a folder named IR_TRACKERv3
- Put these three files inside it:
- IR_TRACKERv3.ino
- IR_TRACKERv3.h
- IR_TRACKERv3.cpp
- Open IR_TRACKERv3.ino in the Arduino IDE
- Compile and upload to the Teensy
How they work together
- .ino = startup entry points: setup() and loop()
- .h = declarations and shared constants
- .cpp = the actual code
So yes, the .ino and .h file prepare the .cpp to compile as one project.
Questions:
- I have followed the above instructions exactly and I keep getting error messages saying "IR_TRACKERv3.h.ino file not found."
- I have tried copying IR_TRACKERv3.ino, IR_TRACKERv3.cpp, and IR_TRACKERv3.h into the IR_TRACKERv3.ino file and still get the same error.
I would appreciate more elementary instructions - I think I need to be shown what the project looks like in the Arduino IDE (folder layout, which file is opened, etc.). Can you help with that?
Additionally, can you produce a cleaner V4 of IR_TRACKERv3 that adds:
- explicit parsing of "BEARING,123" and "COUNT,18"
- a simple checksum packet format
- a more robust non-blocking UART state machine
Context: this Teensy sketch is intended to run on a Teensy 4.1 that will act as the EZB (robot controller) communicating with ARC (Synthiam ARC) over serial. The Teensy will handle the real-time hardware layer (servo, IR sensor, serial commands) while ARC would send high-level commands and receive bearing/count updates.
Thanks, Jack

@Athena. Thank you for v4.1. Attempting to compile, I get this error message:
Can you advise and rewrite the sketch causing this error?
Let me get v4.1 working well with the ARC script before v4.2. Thank you. Jack
Yes - that error is happening because
setup()andloop()are defined twice:IR_TRACKERv4.1.inoIR_TRACKERv4.1.cppWhy this happens
In Arduino projects:
.inofile is the sketch entry point.cppfile should contain the support code onlyIf both files define:
then the linker complains about multiple definition.
Fix
Keep
setup()andloop()only in the.inofileRemove them from the
.cppfile entirely.Corrected V4.1 layout
IR_TRACKERv4.1.inoIR_TRACKERv4.1.hIR_TRACKERv4.1.cppWhat changed
setup()andloop()are now only in the.ino.cppfile no longer duplicates themServois capitalized correctly$\nARC side example
Here’s a simple ARC JavaScript example for sending and reading packets.
If you want, I can do the next step and make this even cleaner by splitting the parser into a dedicated packet handler and giving you a true ARC project setup using the Script skill plus a COM connection skill.
@Athena. Thank you for v4.1. I think we're getting closer to the final product. I do however get the following error when I compile:
Can you rewrite to correct this error?
Can you do the next step and make this even cleaner by splitting the parser into a dedicated packet handler and giving you a true ARC project setup using the Script skill plus a COM connection skill? Let's call this v4.2. Thank you. Jack
Sorry, 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:
Thanks. Jack