MPU6050 Accelerometer Gyro Temperature icon MPU6050 Accelerometer Gyro Temperature — Reads MPU6050 gyro, accelerometer and temperature via I2C, initializes and returns data on-demand to EZ-Script variables using ControlCommand RunOnce Try it →
Asked
Resolved Resolved by Athena!
1 Participant
IR_TRACKERv3 Compile and V4 Upgrade

IR_Trackerv3 Compile And V4 Upgrade

  • 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:

  1. How to use the sketches

Yes - these are one Teensy project, not separate uploads.

You do this:

  1. Create a folder named IR_TRACKERv3
  2. Put these three files inside it:
    • IR_TRACKERv3.ino
    • IR_TRACKERv3.h
    • IR_TRACKERv3.cpp
  3. Open IR_TRACKERv3.ino in the Arduino IDE
  4. 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:

  1. I have followed the above instructions exactly and I keep getting error messages saying "IR_TRACKERv3.h.ino file not found."
  2. 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



Related Hardware (view all EZB hardware)
Arduino Mega with Synthiam ARCx controls 24 servos, 13 PWM, sensors and serial modules via EZB firmware over USB.
USB
Servos 24
Camera
Audio
UART 3
I2C
ADC 8
Digital 21

Related Robot Skills (view all robot skills)
Script by Synthiam
Multi-language ARC Script: build, run and debug Blockly, JavaScript, EZ-Script or Python with Intellisense, run/save/load and Roboscratch support
Configure EZ-B v4/2 Comm Expansion: switch between UART transparent proxy and USB direct PC link (avoids WiFi); EZ-B needs separate power.

ARC Pro

Upgrade to ARC Pro

Experience early access to the latest features and updates. You'll have everything that is needed to unleash your robot's potential.

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#1  

Yes - I can help with both parts:

  1. get IR_TRACKERv3 compiling in the Arduino IDE / Teensyduino, and
  2. outline a cleaner V4 structure with better serial parsing and a non-blocking state machine.

First: the "IR_TRACKERv3.h.ino file not found" error

That error usually means the Arduino IDE is trying to compile a file that is not laid out as a normal sketch project.

What the folder should look like

You want a folder named:

IR_TRACKERv3/

Inside that folder, these files should exist side-by-side:

IR_TRACKERv3/
  IR_TRACKERv3.ino
  IR_TRACKERv3.h
  IR_TRACKERv3.cpp

What should be opened in Arduino IDE

Open only this file:

IR_TRACKERv3.ino

Not the .h, not the .cpp, and not a file pasted into another file.

Important: do not paste all files into the .ino

If you copied the .ino, .cpp, and .h contents into one .ino, the compiler will usually break because:

  • declarations get duplicated
  • the include order gets wrong
  • the sketch no longer matches the expected Arduino structure

A very simple way to set it up

  1. Create a folder called IR_TRACKERv3
  2. Inside it, create these 3 files:
    • IR_TRACKERv3.ino
    • IR_TRACKERv3.h
    • IR_TRACKERv3.cpp
  3. Put the correct content into each file
  4. Double-click IR_TRACKERv3.ino to open it in Arduino IDE
  5. Compile

Option B - even easier

If Arduino IDE is confusing the file names, create the sketch from inside the IDE:

  1. File New
  2. Save the sketch immediately as IR_TRACKERv3
  3. Then use the little dropdown/tab area to add:
    • a new tab named IR_TRACKERv3.h
    • a new tab named IR_TRACKERv3.cpp

That avoids accidental filename issues.


Why you may be seeing IR_TRACKERv3.h.ino

That specific message often happens when:

  • the file was accidentally named IR_TRACKERv3.h.ino
  • Windows hid file extensions
  • the sketch folder name and file names got mixed up

Check this carefully in Windows

Make sure the file is really named:

IR_TRACKERv3.h

and not:

IR_TRACKERv3.h.txt
IR_TRACKERv3.h.ino

Windows often hides extensions, so this is a common trap.


About the two lines you want removed

You mentioned removing this kind of line from both functions:

bool reflectorDetected = (irNorm IR_THRESHOLD);

I don’t see the exact operator in your pasted code because the snippet is truncated, but in general, yes - if you want that removed from:

  • doNegSearchInit()
  • doPosSearchInit()

then those functions need to be rewritten so reflector detection is handled another way, or directly inlined.


Cleaner V4 direction

Yes, a V4 would be a good idea for this.

The improvements you asked for:

  • explicit parsing of:
    • BEARING,123
    • COUNT,18
  • a simple checksum packet format
  • a more robust non-blocking UART state machine

That is exactly the kind of refactor that will make this much easier to maintain and less fragile.


A simple pattern that works well over serial is:

<BEARING,123*45>
<COUNT,18*2A>
<START*7C>
<STOP*51>

Where:

  • < = start of packet
  • > = end of packet
  • *XX = checksum in hex

That gives you:

  • framing
  • corruption detection
  • reliable parsing

Non-blocking UART state machine

Instead of waiting for a full line in one go, the Teensy should:

  • read bytes as they arrive
  • build a packet buffer
  • detect start/end markers
  • validate checksum
  • dispatch by command name

That way the tracker loop keeps running smoothly.


ARC side note

Since this Teensy is acting as the hardware layer for ARC, you may also want to expose the serial protocol clearly so ARC can send simple commands and read back status updates reliably. If you later want, I can help you make the ARC side talk to this Teensy using JavaScript in a Script skill, which tends to be easier to maintain than older script styles.

ARC’s Script skill supports JavaScript and Python: https://synthiam.com/Support/Skills/Scripting/Script?id=16089


What I need from you to fix this cleanly

Please paste these three files exactly as they exist now:

  1. IR_TRACKERv3.ino
  2. IR_TRACKERv3.h
  3. IR_TRACKERv3.cpp

And tell me:

  • are you using Arduino IDE 1.8.x or Arduino IDE 2.x?
  • are you compiling with Teensyduino?
  • do you want me to:
    1. fix V3 so it compiles, or
    2. write a fresh V4 structure from scratch?

If you want, I can also give you a minimal Arduino folder example with the exact text that should go into each file.

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#2  

Thank you @Athena. Your question:

What I need from you to fix this cleanly

Please paste these three files exactly as they exist now:

  1. IR_TRACKERv3.ino
  2. IR_TRACKERv3.h
  3. IR_TRACKERv3.cpp

IR_TRACKERv3.ino

//2) IR_TRACKERv3.ino

#include "IR_TRACKERv3.h"

void setup() {
  setupTracker();
}

void loop() {
  loopTracker();
}

IR_TRACKERv3.h

//3) IR_TRACKERv3.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
};

// -------------------- PINS --------------------
static const uint8_t SERVO_PIN = 29;
static const uint8_t IR_PIN = A14;

// Teensy 4.1 UART8 pins:
// TX8 = pin 35
// RX8 = pin 34

// -------------------- servo CALIBRATION --------------------
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;

// -------------------- BEHAVIOR --------------------
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;

// -------------------- IR --------------------
static const int IR_THRESHOLD = 15;
static const int IR_SMOOTH_SAMPLES = 8;
static const int ADC_MAX_RAW = 4095;

// -------------------- LIMITS --------------------
static const long NEG_OBS_MAX = 2000;
static const long POS_OBS_MAX = 2000;

// -------------------- UART --------------------
static const unsigned long EZB_BAUD = 115200;

// -------------------- COMMANDS --------------------
static const char* CMD_START = "START";
static const char* CMD_STOP = "STOP";
static const char* CMD_BEARING = "BEARING";
static const char* CMD_COUNT = "COUNT";

extern servo trackingServo;
extern TrackerState state;

extern int desiredBearing;
extern int roundedBearing;
extern int actualBearing;
extern int bearing;

extern int lastActualBearing;
extern int lastPublishedBearing;
extern int prevLastPublishedBearing;

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 handleSerialCommand(const String& cmd);
void pollSerial();

void sendToEZB(const String& msg);
void sendBearingUpdate();
void sendBearingCount();

void stopNow(const char* reason);

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 processTrackerState();

IR_TRACKERv3.cpp

//1) Updated IR_TRACKERv3.cpp 

#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();
}

Every time I create any of the above files thru the IDE, they ALL incorporate (.ino) after each file name. Such as

  1. IR_TRACKERv3.ino --------------- It becomes: IR_TRACKERv3.ino.ino
  2. IR_TRACKERv3.h --------------- It becomes: IR_TRACKERv3.h.ino
  3. IR_TRACKERv3.cpp --------------- It becomes: IR_TRACKERv3.cpp.ino

Your questions:

  • are you using Arduino IDE 1.8.x or Arduino IDE 2.x?

Answer: I am using Arduino IDE 2x.

  • are you compiling with Teensyduino?

Answer: I am using a Teensy 4.1

  • do you want me to:
    1. fix V3 so it compiles, or
    2. write a fresh V4 structure from scratch?

Answer: Please write a fresh V4 structure from scratch.

Can you please write a minimal Arduino folder example with the exact text that should go into each file?

Thank you for your help. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#3  

Absolutely - and the filename issue explains the compile error.

Why every file becomes *.ino

In Arduino IDE 2.x, if you create a new tab/file from within the sketch, it often defaults to adding .ino to the name. That’s why you’re seeing things like:

  • IR_TRACKERv3.ino.ino
  • IR_TRACKERv3.h.ino
  • IR_TRACKERv3.cpp.ino

That is not the structure you want.


The easiest fix

Use one .ino file and put the code into tabs

For Arduino IDE 2.x, the cleanest approach is:

  • one main file named IR_TRACKERv4.ino
  • optionally additional tabs, but I recommend keeping V4 in one sketch file first until it compiles cleanly

That avoids filename confusion entirely.


Minimal Arduino folder example

Folder name

Create a folder called:

IR_TRACKERv4

Inside that folder, there should be:

IR_TRACKERv4/IR_TRACKERv4.ino

That’s it for the minimal version.


Exact text for the minimal V4 file

IR_TRACKERv4.ino

#include <Servo.h>

constexpr uint8_t SERVO_PIN = 29;
constexpr uint8_t IR_PIN = A14;

constexpr int SERVO_MIN_ANGLE = 5;
constexpr int SERVO_MAX_ANGLE = 175;
constexpr int SERVO_MIN_PULSE = 500;
constexpr int SERVO_MAX_PULSE = 2500;

constexpr int DESIRED_BEARING = 90;
constexpr int STEP_DELTA = 1;
constexpr int RUNAWAY_MARGIN = 20;

constexpr int IR_THRESHOLD = 15;
constexpr int IR_SMOOTH_SAMPLES = 8;
constexpr int ADC_MAX_RAW = 4095;

constexpr unsigned long EZB_BAUD = 115200;

Servo trackingServo;

enum TrackerState {
  NEG_SEARCH_INIT,
  NEG_SEARCH_FINAL,
  POS_SEARCH_INIT,
  POS_SEARCH_FINAL,
  TRACKER_STOPPED
};

TrackerState state = NEG_SEARCH_INIT;

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 = true;

static int irSamples[IR_SMOOTH_SAMPLES];
static int irSampleIndex = 0;
static bool irInitialized = false;

static String serialLine;

int clampServoAngle(int angle) {
  if (angle < SERVO_MIN_ANGLE) return SERVO_MIN_ANGLE;
  if (angle > SERVO_MAX_ANGLE) return SERVO_MAX_ANGLE;
  return angle;
}

void setServoBearing(int angle) {
  bearing = clampServoAngle(angle);
  trackingServo.write(bearing);
}

int readServo() {
  return bearing;
}

int normalizeIRTo255(int raw) {
  if (raw < 0) raw = 0;
  if (raw > ADC_MAX_RAW) raw = 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;
  }

  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);
}

uint8_t simpleChecksum(const String& s) {
  uint8_t sum = 0;
  for (size_t i = 0; i < s.length(); i++) {
    sum ^= (uint8_t)s[i];
  }
  return sum;
}

String toHex2(uint8_t v) {
  const char* digits = "0123456789ABCDEF";
  String out;
  out += digits[(v >> 4) & 0x0F];
  out += digits[v & 0x0F];
  return out;
}

void sendPacket(const String& payload) {
  uint8_t chk = simpleChecksum(payload);
  Serial8.print('$');
  Serial8.print(payload);
  Serial8.print('*');
  Serial8.print(toHex2(chk));
  Serial8.print('\n');
}

void sendBearingUpdate() {
  sendPacket("BEARING," + String(actualBearing));
}

void sendBearingCount() {
  sendPacket("COUNT," + String(bearingCount));
}

void stopNow(const char* reason) {
  Serial.print("STOP: ");
  Serial.println(reason);
  setServoBearing(roundedBearing);
  sendPacket("STOP");
  sendBearingCount();
  stopRequested = true;
  state = TRACKER_STOPPED;
}

void publishBearing(int candidate) {
  roundedBearing = candidate;

  static int lastActualBearing = -1;
  static int lastPublishedBearing = -1;
  static int prevLastPublishedBearing = -1;

  if (roundedBearing == lastActualBearing) {
    if (roundedBearing != lastPublishedBearing && roundedBearing != prevLastPublishedBearing) {
      prevLastPublishedBearing = lastPublishedBearing;
      lastPublishedBearing = roundedBearing;
      actualBearing = roundedBearing;
      Serial.print("actualBearing: ");
      Serial.println(actualBearing);
      sendBearingUpdate();
    }
  } else {
    lastActualBearing = roundedBearing;
  }
}

void updateBearingFromInitEdges() {
  if (negInit >= 0 && posInit >= 0) {
    int candidate = (negInit + posInit) / 2;
    bearingCount++;
    publishBearing(candidate);
    Serial.print("bearingCount: ");
    Serial.println(bearingCount);
  }
}

void updateBearingFromFinalEdges() {
  if (negFinal >= 0 && posFinal >= 0) {
    int candidate = (negFinal + posFinal) / 2;
    bearingCount++;
    publishBearing(candidate);
    Serial.print("bearingCount: ");
    Serial.println(bearingCount);
  }
}

void beginNegSweep() {
  setServoBearing(roundedBearing);
  if (negObsTot > 2000) {
    stopNow("NEG OBS total exceeded max");
    return;
  }
  if (posObsTot > 2000) {
    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() {
  setServoBearing(roundedBearing);
  posInit = -1;
  posFinal = -1;
  posAbortLimit = roundedBearing + RUNAWAY_MARGIN;
  state = POS_SEARCH_INIT;
}

void handleRunaway() {
  runawayCount++;
  Serial.print("runawayCount: ");
  Serial.println(runawayCount);

  setServoBearing(roundedBearing);

  if (state == NEG_SEARCH_INIT) {
    beginPosSweep();
    Serial.println("POS SWEEP");
  } else {
    beginNegSweep();
    Serial.println("NEG SWEEP");
  }

  if (runawayCount >= 3) {
    stopNow("Runaway limit reached");
  }
}

void doNegSearchInit() {
  int current = readServo();
  int irRaw = readIRSmooth();
  int irNorm = normalizeIRTo255(irRaw);

  if (current < negAbortLimit) {
    handleRunaway();
    return;
  }

  bool reflectorDetected = (irNorm >= IR_THRESHOLD);

  if (reflectorDetected) {
    posInit = current;
    runawayCount = 0;
    updateBearingFromInitEdges();
    state = POS_SEARCH_FINAL;
  } else {
    setServoBearing(current + STEP_DELTA);
  }
}

void doNegSearchFinal() {
  int current = readServo();
  int irRaw = readIRSmooth();
  int irNorm = normalizeIRTo255(irRaw);

  bool reflectorDetected = (irNorm >= IR_THRESHOLD);

  if (reflectorDetected) {
    negFinal = current;
    runawayCount = 0;
    updateBearingFromFinalEdges();
    state = NEG_SEARCH_INIT;
  } else {
    setServoBearing(current - STEP_DELTA);
  }
}

void doPosSearchInit() {
  int current = readServo();
  int irRaw = readIRSmooth();
  int irNorm = normalizeIRTo255(irRaw);

  if (current > posAbortLimit) {
    handleRunaway();
    return;
  }

  bool reflectorDetected = (irNorm >= IR_THRESHOLD);

  if (reflectorDetected) {
    negInit = current;
    runawayCount = 0;
    updateBearingFromInitEdges();
    state = NEG_SEARCH_FINAL;
  } else {
    setServoBearing(current - STEP_DELTA);
  }
}

void doPosSearchFinal() {
  int current = readServo();
  int irRaw = readIRSmooth();
  int irNorm = normalizeIRTo255(irRaw);

  bool reflectorDetected = (irNorm >= IR_THRESHOLD);

  if (reflectorDetected) {
    posFinal = current;
    runawayCount = 0;
    updateBearingFromFinalEdges();
    state = POS_SEARCH_INIT;
  } else {
    setServoBearing(current + STEP_DELTA);
  }
}

void handleSerialCommand(const String& cmd) {
  String s = cmd;
  s.trim();
  s.toUpperCase();

  if (s == "START") {
    startRequested = true;
    stopRequested = false;
    Serial.println("START OK");
    return;
  }

  if (s == "STOP") {
    stopNow("Serial STOP");
    return;
  }

  if (s.startsWith("BEARING,")) {
    int value = s.substring(8).toInt();
    roundedBearing = clampServoAngle(value);
    setServoBearing(roundedBearing);
    Serial.print("BEARING SET ");
    Serial.println(roundedBearing);
    return;
  }

  if (s.startsWith("COUNT,")) {
    int value = s.substring(6).toInt();
    bearingCount = value;
    Serial.print("COUNT SET ");
    Serial.println(bearingCount);
    return;
  }

  if (s == "BEARING?") {
    sendBearingUpdate();
    return;
  }

  if (s == "COUNT?") {
    sendBearingCount();
    return;
  }

  Serial.print("Unknown cmd: ");
  Serial.println(s);
}

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;
      if (serialLine.length() > 80) {
        serialLine = "";
      }
    }
  }
}

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;
  }
}

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_TRACKERv4 READY");
  Serial.println("Teensy Serial8 active on TX8=pin 35, RX8=pin 34");
}

void loopTracker() {
  pollSerial();
  processTrackerState();
}

void setup() {
  setupTracker();
}

void loop() {
  loopTracker();
}

Important note about this V4 example

This is a single-file minimal build so you can get compiling first.

Once that compiles, I would recommend splitting it into:

  • IR_TRACKERv4.ino
  • IR_TRACKERv4.h
  • IR_TRACKERv4.cpp

But only after the single-file version is confirmed working.


Why I recommend this order

Right now the problem is not your tracker logic alone - it’s also the Arduino IDE file behavior. So the fastest path is:

  1. get a single-file sketch compiling
  2. verify Teensy 4.1 + servo + IR + Serial8 behavior
  3. then split into .h/.cpp if you still want a multi-file project

That prevents file naming issues from hiding actual code issues.


If you want the 3-file version next

I can absolutely write you a proper V4 three-file layout with:

  • IR_TRACKERv4.ino
  • IR_TRACKERv4.h
  • IR_TRACKERv4.cpp

and make it structured so it compiles cleanly in Arduino IDE 2.x.

If you want that, I’ll format it very explicitly like this:

IR_TRACKERv4/
  IR_TRACKERv4.ino
  IR_TRACKERv4.h
  IR_TRACKERv4.cpp

with the exact contents for each file.


One more helpful note for ARC

Because ARC is going to be talking to this Teensy over serial, a tiny Script skill in ARC using JavaScript can make testing much easier. If you want, I can also give you the ARC side code to send START, STOP, BEARING,123, and parse the checksum packets.

If you’d like, I can now generate the clean 3-file V4 version in a format you can paste directly into Arduino IDE 2.x.

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#4  

@Athena. I was able to finally get a compile by using this example:

Option B - even easier

If Arduino IDE is confusing the file names, create the sketch from inside the IDE:

  1. File New
  2. Save the sketch immediately as IR_TRACKERv3
  3. Then use the little dropdown/tab area to add:
    • a new tab named IR_TRACKERv3.h
    • a new tab named IR_TRACKERv3.cpp

I got the following error for the compile of IR_TRACKERv3:

In file included from C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.ino:2:
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.h:53:8: error: 'servo' does not name a type; did you mean 'Servo'?
   53 | extern servo trackingServo;
      |        ^~~~~
      |        Servo
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.h:51:20: warning: 'CMD_COUNT' defined but not used [-Wunused-variable]
   51 | static const char* CMD_COUNT = "COUNT";
      |                    ^~~~~~~~~
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.h:50:20: warning: 'CMD_BEARING' defined but not used [-Wunused-variable]
   50 | static const char* CMD_BEARING = "BEARING";
      |                    ^~~~~~~~~~~
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.h:49:20: warning: 'CMD_STOP' defined but not used [-Wunused-variable]
   49 | static const char* CMD_STOP = "STOP";
      |                    ^~~~~~~~
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.h:48:20: warning: 'CMD_START' defined but not used [-Wunused-variable]
   48 | static const char* CMD_START = "START";
      |                    ^~~~~~~~~
In file included from C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.cpp:2:
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.h:53:8: error: 'servo' does not name a type; did you mean 'Servo'?
   53 | extern servo trackingServo;
      |        ^~~~~
      |        Servo
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.cpp:38:22: warning: 'lastStepMs' defined but not used [-Wunused-variable]
   38 | static unsigned long lastStepMs = 0;
      |                      ^~~~~~~~~~
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.h:51:20: warning: 'CMD_COUNT' defined but not used [-Wunused-variable]
   51 | static const char* CMD_COUNT = "COUNT";
      |                    ^~~~~~~~~
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv3\IR_TRACKERv3.h:50:20: warning: 'CMD_BEARING' defined but not used [-Wunused-variable]
   50 | static const char* CMD_BEARING = "BEARING";
      |                    ^~~~~~~~~~~
Multiple libraries were found for "Servo.h"
  Used: C:\Users\jackl\AppData\Local\Arduino15\packages\teensy\hardware\avr\1.62.0\libraries\Servo
  Not used: C:\Users\jackl\AppData\Local\Arduino15\libraries\Servo
exit status 1

Compilation error: 'servo' does not name a type; did you mean 'Servo'?

Can you evaluate the error message for v3 and adjust v4 to compile without this error? Thanks. Jack

#5  

@Athena. IR_TRACKERv4 (single sketch) compiles perfectly with no error msgs. How ever, please change the following:

UNDER: Function doNegSearchInit()

CHANGE TO THIS:

*  bool reflectorDetected = (irNorm < = IR\_THRESHOLD); 
* 

UNDER: Function deNegSearchFinal()

CHANGE TO THIS:

* bool reflectorDetected = (irNorm > IR\_THRESHOLD); 
* DELETE: runawayCount = 0;

UNDER: Function doPosSearchInit()

CHANGE TO THIS:

*  bool reflectorDetected = (irNorm < = IR\_THRESHOLD); 

UNDER: Function doPosSearchFinal()

CHANGE TO THIS:

* bool reflectorDetected = (irNorm > IR\_THRESHOLD); 
* DELETE: runawayCount = 0;

I am ready for the multi sketch v4. Please send the ARC side code to send START, STOP, BEARING,123, and parse the checksum packets. Please generate the clean 3-file V4 version in a format you can paste directly into Arduino IDE 2.x.

Thanks, Jack.

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#6  

Great - that compile error is clear, and yes, we can fix it in V4.

What caused the V3 error

This line in your header is the problem:

extern servo trackingServo;

It must be:

extern servo trackingServo;

Also, the header needs #include <Servo.h> before that declaration.

The warnings about CMD_START, CMD_STOP, etc. are harmless, but in a cleaner V4 I’d remove those constants unless they’re actually used.


Clean multi-file V4 for Arduino IDE 2.x

Below is a 3-file sketch layout you can paste directly into Arduino IDE 2.x.

Folder / sketch layout

Create a sketch named:

IR_TRACKERv4

Then add these tabs/files:

IR_TRACKERv4.ino
IR_TRACKERv4.h
IR_TRACKERv4.cpp

1) IR_TRACKERv4.ino

#include "IR_TRACKERv4.h"

void setup() {
  setupTracker();
}

void loop() {
  loopTracker();
}

2) IR_TRACKERv4.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
};

// -------------------- PINS --------------------
static const uint8_t SERVO_PIN = 29;
static const uint8_t IR_PIN = A14;

// Teensy 4.1 UART8 pins:
// TX8 = pin 35
// RX8 = pin 34

// -------------------- servo CALIBRATION --------------------
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;

// -------------------- BEHAVIOR --------------------
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;

// -------------------- IR --------------------
static const int IR_THRESHOLD = 15;
static const int IR_SMOOTH_SAMPLES = 8;
static const int ADC_MAX_RAW = 4095;

// -------------------- LIMITS --------------------
static const long NEG_OBS_MAX = 2000;
static const long POS_OBS_MAX = 2000;

// -------------------- UART --------------------
static const unsigned long EZB_BAUD = 115200;

// -------------------- ARC COMMANDS / PACKETS --------------------
// Packet format:
//   $BEARING,123*hh
//   $COUNT,18*hh
//   $STOP*hh
// where hh is 2-digit hex checksum of the payload only.

extern servo trackingServo;
extern TrackerState state;
extern int desiredBearing;
extern int roundedBearing;
extern int actualBearing;
extern int bearing;
extern int lastActualBearing;
extern int lastPublishedBearing;
extern int prevLastPublishedBearing;
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 handleSerialCommand(const String& cmd);
void pollSerial();
void sendToEZB(const String& msg);
void sendBearingUpdate();
void sendBearingCount();
void stopNow(const char* reason);

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 processTrackerState();

3) IR_TRACKERv4.cpp

#include "IR_TRACKERv4.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 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;
  }

  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);
}

static uint8_t simpleChecksum(const String& payload) {
  uint8_t sum = 0;
  for (size_t i = 0; i < payload.length(); i++) {
    sum += (uint8_t)payload[i];
  }
  return sum;
}

static String toHex2(uint8_t v) {
  const char* hex = "0123456789ABCDEF";
  String out;
  out += hex[(v >> 4) & 0x0F];
  out += hex[v & 0x0F];
  return out;
}

void sendToEZB(const String& msg) {
  String payload = msg;
  uint8_t chk = simpleChecksum(payload);

  Serial.print("$");
  Serial.print(payload);
  Serial.print("*");
  Serial.println(toHex2(chk));
}

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);

  // requested change: explicit threshold check
  bool reflectorDetected = (irNorm >= IR_THRESHOLD);

  if (current < negAbortLimit) {
    handleRunaway();
    return;
  }

  if (reflectorDetected) {
    posInit = current;
    updateBearingFromInitEdges();
    state = POS_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();
    state = NEG_SEARCH_INIT;
  } else {
    int next = current - STEP_DELTA;
    setServoBearing(next);
    delay(SETTLE_DELAY_MS);
  }
}

void doPosSearchInit() {
  int current = readServo();
  int irRaw = readIRSmooth();
  int irNorm = normalizeIRTo255(irRaw);

  // requested change: explicit threshold check
  bool reflectorDetected = (irNorm >= IR_THRESHOLD);

  if (current > posAbortLimit) {
    handleRunaway();
    return;
  }

  if (reflectorDetected) {
    negInit = current;
    updateBearingFromInitEdges();
    state = NEG_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();
    state = POS_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 == "START") {
    startRequested = true;
    stopRequested = false;
    Serial.println("START OK");
    return;
  }

  if (s == "STOP") {
    stopNow("Serial STOP");
    return;
  }

  if (s.startsWith("BEARING,")) {
    int value = s.substring(8).toInt();
    roundedBearing = clampServoAngle(value);
    setServoBearing(roundedBearing);
    Serial.print("BEARING SET ");
    Serial.println(roundedBearing);
    return;
  }

  if (s.startsWith("COUNT,")) {
    int value = s.substring(6).toInt();
    bearingCount = value;
    Serial.print("COUNT SET ");
    Serial.println(bearingCount);
    return;
  }

  if (s == "BEARING?") {
    sendBearingUpdate();
    return;
  }

  if (s == "COUNT?") {
    sendBearingCount();
    return;
  }

  Serial.print("Unknown cmd: ");
  Serial.println(s);
}

void pollSerial() {
  while (Serial.available() > 0) {
    char c = (char)Serial.read();

    if (c == '\n' || c == '\r') {
      if (serialLine.length() > 0) {
        handleSerialCommand(serialLine);
        serialLine = "";
      }
    } else if (c == '$') {
      serialLine = "";
    } else if (c == '*') {
      // For this version, treat checksum delimiter as end marker
      if (serialLine.length() > 0) {
        handleSerialCommand(serialLine);
        serialLine = "";
      }
    } else {
      serialLine += c;
      if (serialLine.length() > 80) {
        serialLine = "";
      }
    }
  }
}

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(" negObsTot=");
    Serial.print(negObsTot);
    Serial.print(" posObsTot=");
    Serial.println(posObsTot);
  }
}

void setupTracker() {
  Serial.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 = true;
  beginNegSweep();

  Serial.println("IR_TRACKERv4 READY");
}

void loopTracker() {
  pollSerial();
  processTrackerState();
}

void setup() {
  setupTracker();
}

void loop() {
  loopTracker();
}

ARC side JavaScript

Here’s a simple ARC Script skill example that sends commands and parses the packet format.

Assumptions

  • You have a Script skill in ARC
  • You’re communicating with the Teensy over a serial port skill or a COM port connection in ARC
  • Replace "COM3" with your actual port
var port = "COM3";

COM.open(port, 115200);

function sendCmd(cmd) {
  COM.writeStringNewLine(port, cmd);
  print("Sent: " + cmd);
}

function readPackets() {
  while (COM.available(port) > 0) {
    var line = COM.readLine(port);
    if (line == null || line == "") {
      continue;
    }

    line = line.trim();
    print("RX: " + line);

    if (line.charAt(0) != '$') {
      continue;
    }

    var starIndex = line.indexOf('*');
    if (starIndex < 0) {
      continue;
    }

    var payload = line.substring(1, starIndex);
    var chkText = line.substring(starIndex + 1).trim();

    var chk = 0;
    for (var i = 0; i < payload.length; i++) {
      chk += payload.charCodeAt(i);
    }
    chk = chk & 0xFF;

    var rxChk = parseInt(chkText, 16);
    if (isNaN(rxChk)) {
      print("Bad checksum field: " + chkText);
      continue;
    }

    if (chk != rxChk) {
      print("Checksum mismatch. payload=" + payload + " calc=" + chk + " rx=" + rxChk);
      continue;
    }

    var parts = payload.split(",");
    var cmd = parts[0];

    if (cmd == "BEARING" && parts.length > 1) {
      var bearing = parseInt(parts[1]);
      print("Bearing update: " + bearing);
    } else if (cmd == "COUNT" && parts.length > 1) {
      var count = parseInt(parts[1]);
      print("Count update: " + count);
    } else if (cmd == "STOP") {
      print("Tracker stopped");
    } else {
      print("Payload: " + payload);
    }
  }
}

sendCmd("START");
sendCmd("BEARING,123");
sendCmd("COUNT,18");

// poll packets in a loop
while (true) {
  readPackets();
  sleep(50);
}

Important note about the checksum parser

In the Arduino code above, I made the packet structure ready for checksums, but the parser is still intentionally simple.

If you want, I can do the next upgrade and make the Teensy parser fully validate packets like:

$BEARING,123*5A

instead of only using * as a delimiter.

That would be the cleanest true V4.


About your requested reflectorDetected lines

You asked for:

  • bool reflectorDetected = (irNorm >= IR_THRESHOLD);
  • remove runawayCount = 0;

I already applied that structure in the V4 above:

  • explicit threshold comparison is used
  • runawayCount = 0; is removed from those two functions

My recommendation

Your current V4 compiles, so the best next step is:

  1. test this 3-file version
  2. confirm ARC can send/receive packets
  3. then I can give you a true checksum-validated UART parser V4.1

If you want, I can do that next and make the protocol fully robust with:

  • start marker
  • payload
  • checksum validation
  • timeout/reset on malformed packets
  • ARC-ready example messages
I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#7  

@Athena. Thank you for v4. Error message is copied below:

In file included from C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv4\IR_TRACKERv4.ino:1:
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv4\IR_TRACKERv4.h:54:8: error: 'servo' does not name a type; did you mean 'Servo'?
   54 | extern servo trackingServo;
      |        ^~~~~
      |        Servo
In file included from C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv4\IR_TRACKERv4.cpp:1:
C:\Users\jackl\OneDrive\Documents\Arduino\IR_TRACKERv4\IR_TRACKERv4.h:54:8: error: 'servo' does not name a type; did you mean 'Servo'?
   54 | extern servo trackingServo;
      |        ^~~~~
      |        Servo
Multiple libraries were found for "Servo.h"
  Used: C:\Users\jackl\AppData\Local\Arduino15\packages\teensy\hardware\avr\1.62.0\libraries\Servo
  Not used: C:\Users\jackl\AppData\Local\Arduino15\libraries\Servo
exit status 1

Compilation error: 'servo' does not name a type; did you mean 'Servo'?

Can you explain and correct the problem? Also, see the corrected v4 Functions below; From IR_TRACKERv4.cpp

void doNegSearchInit() {  int current = readServo();  int irRaw = readIRSmooth();  int irNorm = normalizeIRTo255(irRaw);
  // requested change: explicit threshold check  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 irRaw = readIRSmooth();  int irNorm = normalizeIRTo255(irRaw);  bool reflectorDetected = (irNorm > IR_THRESHOLD);//
  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 irRaw = readIRSmooth();  int irNorm = normalizeIRTo255(irRaw);
  // requested change: explicit threshold check  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 irRaw = readIRSmooth();  int irNorm = normalizeIRTo255(irRaw);  bool reflectorDetected = (irNorm > IR_THRESHOLD);
  if (reflectorDetected) {    posFinal = current;    updateBearingFromFinalEdges();    state = NEG_SEARCH_INIT;//  } else {    int next = current + STEP_DELTA;    setServoBearing(next);    delay(SETTLE_DELAY_MS);  }}

Can you correct v4 logic according to the above sketch for

  1. doNegSearchInit
  2. doNegSearchFinal
  3. doPosSearchInit
  4. doPosSearchFinal

Can you send v4.1 with the above corrections and make the protocol fully robust with:

  • start marker
  • payload
  • checksum validation
  • timeout/reset on malformed packets
  • ARC-ready example messages

Thank you. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#8  

Yes - the error is straightforward:

Why it fails

In your header you still have:

extern servo trackingServo;

Arduino/Teensy uses the class name Servo with a capital S, not servo.

So this must be:

extern servo trackingServo;

And the header must include the servo library before that declaration.


Corrected V4.1 structure

Below is a cleaner, fully robust 3-file version with:

  • start marker
  • payload
  • checksum validation
  • timeout/reset on malformed packets
  • ARC-ready messages

It also corrects the logic in all four search functions.


1) IR_TRACKERv4.ino

#include "IR_TRACKERv4.h"

void setup() {
  setupTracker();
}

void loop() {
  loopTracker();
}

2) IR_TRACKERv4.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
};

// -------------------- PINS --------------------
static const uint8_t SERVO_PIN = 29;
static const uint8_t IR_PIN = A14;

// Teensy 4.1 UART8 pins:
// TX8 = pin 35
// RX8 = pin 34

// -------------------- servo CALIBRATION --------------------
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;

// -------------------- BEHAVIOR --------------------
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;

// -------------------- IR --------------------
static const int IR_THRESHOLD = 15;
static const int IR_SMOOTH_SAMPLES = 8;
static const int ADC_MAX_RAW = 4095;

// -------------------- LIMITS --------------------
static const long NEG_OBS_MAX = 2000;
static const long POS_OBS_MAX = 2000;

// -------------------- UART --------------------
static const unsigned long EZB_BAUD = 115200;

// -------------------- PACKET PARSING --------------------
static const char PACKET_START = '$';
static const char PACKET_END = '\n';
static const char CHECKSUM_SEP = '*';

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 handleSerialCommand(const String& cmd);
void pollSerial();

void sendToEZB(const String& msg);
void sendBearingUpdate();
void sendBearingCount();

void stopNow(const char* reason);

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 processTrackerState();

3) IR_TRACKERv4.cpp

#include "IR_TRACKERv4.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 String serialLine;
static unsigned long lastDebugMs = 0;
static unsigned long lastPacketMs = 0;

static int lastActualBearing = -1;
static int lastPublishedBearing = -1;
static int prevLastPublishedBearing = -1;

// -------------------- Utility --------------------
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;
  }

  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);
}

// -------------------- Packet Handling --------------------
static uint8_t simpleChecksum(const String& payload) {
  uint8_t chk = 0;
  for (unsigned int i = 0; i < payload.length(); i++) {
    chk ^= (uint8_t)payload[i];
  }
  return chk;
}

static String toHex2(uint8_t v) {
  const char hex[] = "0123456789ABCDEF";
  String out;
  out += hex[(v >> 4) & 0x0F];
  out += hex[v & 0x0F];
  return out;
}

void sendToEZB(const String& msg) {
  uint8_t chk = simpleChecksum(msg);
  Serial8.print('$');
  Serial8.print(msg);
  Serial8.print('*');
  Serial8.print(toHex2(chk));
  Serial8.print('\n');

  lastPacketMs = millis();
}

void sendBearingUpdate() {
  sendToEZB("BEARING," + String(actualBearing));
}

void sendBearingCount() {
  sendToEZB("COUNT," + String(bearingCount));
}

static bool parsePacket(const String& packet, String& payloadOut) {
  int star = packet.lastIndexOf('*');
  if (star < 0) {
    return false;
  }

  if (packet.length() < 4) {
    return false;
  }

  String payload = packet.substring(1, star); // skip '$'
  String chkStr = packet.substring(star + 1);

  if (chkStr.length() != 2) {
    return false;
  }

  uint8_t received = (uint8_t)strtol(chkStr.c_str(), nullptr, 16);
  uint8_t computed = simpleChecksum(payload);

  if (received != computed) {
    return false;
  }

  payloadOut = payload;
  return true;
}

// -------------------- Bearing / Publishing --------------------
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() {
  if (negInit >= 0 && posInit >= 0) {
    int candidate = (negInit + posInit) / 2;
    bearingCount++;
    publishBearing(candidate);
    Serial.print("bearingCount: ");
    Serial.println(bearingCount);
  }
}

void updateBearingFromFinalEdges() {
  if (negFinal >= 0 && posFinal >= 0) {
    int candidate = (negFinal + posFinal) / 2;
    bearingCount++;
    publishBearing(candidate);
    Serial.print("bearingCount: ");
    Serial.println(bearingCount);
  }
}

// -------------------- Sweep Control --------------------
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;
  }
}

// -------------------- Corrected Search Logic --------------------
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;
    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 (current > negAbortLimit) {
    handleRunaway();
    return;
  }

  if (reflectorDetected) {
    negFinal = current;
    updateBearingFromFinalEdges();
    state = NEG_SEARCH_INIT;
  } 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;
    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 (current > posAbortLimit) {
    handleRunaway();
    return;
  }

  if (reflectorDetected) {
    posFinal = current;
    updateBearingFromFinalEdges();
    state = NEG_SEARCH_INIT;
  } else {
    int next = current + STEP_DELTA;
    setServoBearing(next);
    delay(SETTLE_DELAY_MS);
  }
}

// -------------------- ARC / Serial Commands --------------------
void handleSerialCommand(const String& cmd) {
  String s = cmd;
  s.trim();
  s.toUpperCase();

  if (s == "START") {
    startRequested = true;
    stopRequested = false;
    Serial.println("START OK");
    return;
  }

  if (s == "STOP") {
    stopNow("Serial STOP");
    return;
  }

  if (s.startsWith("BEARING,")) {
    int value = s.substring(8).toInt();
    roundedBearing = clampServoAngle(value);
    setServoBearing(roundedBearing);
    Serial.print("BEARING SET ");
    Serial.println(roundedBearing);
    return;
  }

  if (s.startsWith("COUNT,")) {
    int value = s.substring(6).toInt();
    bearingCount = value;
    Serial.print("COUNT SET ");
    Serial.println(bearingCount);
    return;
  }

  if (s == "BEARING?") {
    sendBearingUpdate();
    return;
  }

  if (s == "COUNT?") {
    sendBearingCount();
    return;
  }

  Serial.print("Unknown cmd: ");
  Serial.println(s);
}

void pollSerial() {
  static bool inPacket = false;
  static String packet;
  static unsigned long packetStartMs = 0;

  while (Serial8.available() > 0) {
    char c = (char)Serial8.read();

    if (!inPacket) {
      if (c == '$') {
        inPacket = true;
        packet = "$";
        packetStartMs = millis();
      }
      continue;
    }

    if (millis() - packetStartMs > 200) {
      inPacket = false;
      packet = "";
      Serial.println("Packet timeout/reset");
      continue;
    }

    packet += c;

    if (c == '\n') {
      String payload;
      bool ok = parsePacket(packet, payload);

      if (ok) {
        handleSerialCommand(payload);
      } else {
        Serial.print("Bad packet: ");
        Serial.println(packet);
      }

      packet = "";
      inPacket = false;
    }

    if (packet.length() > 100) {
      packet = "";
      inPacket = false;
      Serial.println("Packet overflow/reset");
    }
  }
}

// -------------------- Main State Machine --------------------
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(" 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);
  startRequested = true;

  beginNegSweep();

  Serial.println("IR_TRACKERv4 READY");
  Serial.println("Teensy Serial8 active on TX8=pin 35, RX8=pin 34");
}

void loopTracker() {
  pollSerial();
  processTrackerState();
}

void setup() {
  setupTracker();
}

void loop() {
  loopTracker();
}

ARC example in JavaScript

Here’s a simple ARC Script skill example for sending commands and reading packets.

var port = "COM3"; // change to your serial port

COM.open(port, 115200);

function sendCmd(cmd) {
  var payload = cmd;
  var checksum = 0;

  for (var i = 0; i < payload.length; i++) {
    checksum = checksum ^ payload.charCodeAt(i);
  }

  var hex = checksum.toString(16).toUpperCase();
  if (hex.length < 2) {
    hex = "0" + hex;
  }

  var packet = "$" + payload + "*" + hex + "\n";
  COM.writeString(port, packet);
  print("TX: " + packet);
}

function readPackets() {
  while (COM.available(port) > 0) {
    var line = COM.readLine(port);
    if (line == null || line == "") {
      continue;
    }

    line = line.trim();
    print("RX: " + line);

    if (line.charAt(0) != '$') {
      continue;
    }

    var starIndex = line.indexOf('*');
    if (starIndex < 0) {
      continue;
    }

    var payload = line.substring(1, starIndex);
    var parts = payload.split(',');

    if (parts.length >= 2) {
      var cmd = parts[0];

      if (cmd == "BEARING") {
        print("Bearing = " + parts[1]);
      } else if (cmd == "COUNT") {
        print("Count = " + parts[1]);
      } else if (cmd == "STOP") {
        print("Tracker stopped");
      } else {
        print("Payload = " + payload);
      }
    } else if (payload == "STOP") {
      print("Tracker stopped");
    }
  }
}

sendCmd("START");
sendCmd("BEARING,123");
sendCmd("COUNT,18");

while (true) {
  readPackets();
  sleep(50);
}

What to do next

If you want, I can do one more pass and make the Teensy parser even stricter by:

  • rejecting packets with malformed hex checksums
  • adding command IDs
  • adding ACK / NACK
  • adding a PING / PONG test packet for ARC

If you’d like that, I can generate v4.2 next.

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
 
📚 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.