Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/firmware.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ jobs:
run: pio run -e teensyLC
working-directory: ./firmware/joystick

- name: Build octoaxes firmware (Teensy 4.1)
run: pio run -e teensy41
working-directory: ./firmware/octoaxes

- name: Run unit tests
run: pio test -e native
working-directory: ./firmware/controller
2 changes: 2 additions & 0 deletions firmware/octoaxes/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.pio
*.json
187 changes: 187 additions & 0 deletions firmware/octoaxes/axesmrg.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#include "axesmrg.h"
#include "build_opt.h"

AxisManager axisManager; // define the global instance

AxisManager::AxisManager() {
axisCount = 0;
// initialize the pointer array to nullptr
for (uint8_t i = 0; i < MAX_AXES; i++) {
axes[i] = nullptr;
}
}

AxisManager::~AxisManager() {
// release resources
for (uint8_t i = 0; i < axisCount; i++) {
if (axes[i] != nullptr) {
delete axes[i];
axes[i] = nullptr;
}
}
}

bool AxisManager::addAxis(Axis* axis) {
if (axisCount >= MAX_AXES || axis == nullptr) {
DEBUG_PRINTLN("Cannot add axis: maximum limit reached or null axis");
return false;
}

axes[axisCount] = axis;
axisCount++;

DEBUG_PRINT("Axis added: ");
DEBUG_PRINTLN(axis->getAxisName()); // fix: use the correct function name getAxisName()
DEBUG_PRINT("Total axes: ");
DEBUG_PRINTLN(axisCount);

return true;
}

bool AxisManager::beginAll() {
DEBUG_PRINTLN("beginAll:START"); // debug point
bool allSuccess = true;

for (uint8_t i = 0; i < axisCount; i++) {
if (axes[i] != nullptr) {
bool success = false;

// select the matching config by axis name
String axisName = String(axes[i]->getAxisName()); // fix: use getAxisName() and convert to String

DEBUG_PRINT("beginAll:INIT_AXIS:");
DEBUG_PRINTLN(axisName); // debug point

// fix: use the equals() method for string comparison
if (axisName.equals("X")) {
success = axes[i]->begin(AxisConfigs::X_AXIS);
} else if (axisName.equals("Y")) {
success = axes[i]->begin(AxisConfigs::Y_AXIS);
} else if (axisName.equals("Z")) {
success = axes[i]->begin(AxisConfigs::Z_AXIS);
} else if (axisName.equals("W")) {
success = axes[i]->begin(AxisConfigs::W_AXIS);
} else if (axisName.equals("Turret")) {
success = axes[i]->begin(AxisConfigs::EXPAND1_AXIS);
} else if (axisName.equals("E3")) {
success = axes[i]->begin(AxisConfigs::EXPAND3_AXIS);
} else if (axisName.equals("E4")) {
success = axes[i]->begin(AxisConfigs::EXPAND4_AXIS);
} else if (axisName.equals("W2")) {
// W2 = the second filter wheel, reusing the EXPAND4_AXIS config (filter wheel + invert_direction=true).
// Paired with the legacy Squid protocol (AXIS_W2=6 / MOVE_W2=19 / INITFILTERWHEEL_W2=252).
success = axes[i]->begin(AxisConfigs::EXPAND4_AXIS);
} else {
DEBUG_PRINT("Unknown axis configuration for: ");
DEBUG_PRINTLN(axisName);
success = false;
}

DEBUG_PRINT("beginAll:AFTER_BEGIN:");
DEBUG_PRINTLN(axisName); // debug point

if (!success) {
DEBUG_PRINT("Failed to initialize axis: ");
DEBUG_PRINTLN(axisName);
// Compatibility: a begin() failure means the TMC4361A SPI did not respond (board not plugged in /
// chip damaged / broken wiring). Delete this Axis instance and set the slot to nullptr so that later
// findAxisByName returns nullptr, and every handler's if (axis) guard turns the command into a silent
// no-op (the response packet reports any_moving=false and COMPLETED immediately, so the host's
// wait_till_operation_is_completed wakes up at once).
// This avoids later SPI operations hitting a dead chip, wasting the bus, and producing false-positive status.
delete axes[i];
axes[i] = nullptr;
allSuccess = false;
} else {
DEBUG_PRINT("Successfully initialized axis: ");
DEBUG_PRINTLN(axisName);
}
}
}

return allSuccess;
}

void AxisManager::updateAll() {
for (uint8_t i = 0; i < axisCount; i++) {
if (axes[i] != nullptr) {
axes[i]->update();
}
}
}

Axis* AxisManager::findAxisByName(const String& axisName) {
for (uint8_t i = 0; i < axisCount; i++) {
// fix: use getAxisName() and convert to String for comparison
if (axes[i] != nullptr && String(axes[i]->getAxisName()).equals(axisName)) {
return axes[i];
}
}
return nullptr;
}

bool AxisManager::processCommand(const String& command) {
DEBUG_PRINT("AxisMgr:CMD:");
DEBUG_PRINTLN(command); // debug point A - command received

// Command format: "axisName:commandBody", e.g. "E3:HOMING"
int colonIndex = command.indexOf(':');

if (colonIndex == -1) {
DEBUG_PRINTLN("Invalid command format. Expected: AXIS:COMMAND");
return false;
}

String axisName = command.substring(0, colonIndex);
String cmd = command.substring(colonIndex + 1);

axisName.trim();
cmd.trim();

DEBUG_PRINT("AxisMgr:AXIS=");
DEBUG_PRINT(axisName);
DEBUG_PRINT(",CMD=");
DEBUG_PRINTLN(cmd); // debug point B - parse result

if (axisName.length() == 0 || cmd.length() == 0) {
DEBUG_PRINTLN("Empty axis name or command");
return false;
}

// find the matching axis
DEBUG_PRINT("AxisMgr:FIND_AXIS,count=");
DEBUG_PRINTLN(axisCount); // debug point C - axis count

Axis* targetAxis = findAxisByName(axisName);
if (targetAxis == nullptr) {
DEBUG_PRINT("Axis not found: ");
DEBUG_PRINTLN(axisName);
return false;
}

DEBUG_PRINTLN("AxisMgr:AXIS_FOUND"); // debug point D - axis found

// forward the command to the matching axis for handling
bool success = targetAxis->processCommand(cmd);

if (success) {
DEBUG_PRINT("Command '");
DEBUG_PRINT(cmd);
DEBUG_PRINT("' sent to axis ");
DEBUG_PRINTLN(axisName);
} else {
DEBUG_PRINT("Failed to process command '");
DEBUG_PRINT(cmd);
DEBUG_PRINT("' on axis ");
DEBUG_PRINTLN(axisName);
}

return success;
}

Axis* AxisManager::getAxis(uint8_t index) {
if (index < axisCount) {
return axes[index];
}
return nullptr;
}
42 changes: 42 additions & 0 deletions firmware/octoaxes/axesmrg.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#ifndef AXES_MANAGER_H
#define AXES_MANAGER_H

#include <Arduino.h>
#include "axis.h"
#include "config.h"

class AxisManager {
private:
static const uint8_t MAX_AXES = 8; // supports up to 8 axes
Axis* axes[MAX_AXES]; // array of axis object pointers
uint8_t axisCount; // current number of axes

public:
AxisManager();
~AxisManager();

// Add an axis to the manager
bool addAxis(Axis* axis);

// Initialize all axes
bool beginAll();

// Update all axis state machines
void updateAll();

// Process a serial command
bool processCommand(const String& command);

// Get the number of axes
uint8_t getAxisCount() const { return axisCount; }

// Get an axis by index
Axis* getAxis(uint8_t index);

// Find an axis object by name
Axis* findAxisByName(const String& axisName);
};

extern AxisManager axisManager; // global axis manager instance

#endif
Loading
Loading