From 5404e76eb2d7bc72d7d75812244e00a5f3a8878c Mon Sep 17 00:00:00 2001 From: "kevin.wang" Date: Wed, 1 Jul 2026 13:19:26 +0800 Subject: [PATCH 1/2] feat(firmware): add octoaxes multi-axis controller firmware Import the new octoaxes firmware (Teensy 4.1) as firmware/octoaxes. This is a modular rewrite of the motion controller with separate translation units for axes, stepper axis, filter wheel, objectives, illumination, trigger, joystick and serial command processing, plus the bundled TMC IC drivers (TMC2240 / TMC2660 / TMC4361A). Source is imported from the standalone octoaxes repo at its current HEAD; build artifacts (.pio) and generated *.json are excluded via .gitignore. Verified locally with `pio run -e teensy41` (SUCCESS). Co-Authored-By: Claude Opus 4.8 (1M context) --- firmware/octoaxes/.gitignore | 2 + firmware/octoaxes/axesmrg.cpp | 187 +++ firmware/octoaxes/axesmrg.h | 42 + firmware/octoaxes/axis.cpp | 1323 +++++++++++++++ firmware/octoaxes/axis.h | 309 ++++ firmware/octoaxes/build_opt.h | 30 + firmware/octoaxes/commandprocessor.cpp | 587 +++++++ firmware/octoaxes/commandprocessor.h | 68 + firmware/octoaxes/config.h | 573 +++++++ firmware/octoaxes/def_octopi_80120.h | 49 + firmware/octoaxes/download.sh | 29 + firmware/octoaxes/filterwheel.cpp | 270 ++++ firmware/octoaxes/filterwheel.h | 45 + firmware/octoaxes/illumination.cpp | 539 +++++++ firmware/octoaxes/illumination.h | 118 ++ firmware/octoaxes/joystick.cpp | 265 +++ firmware/octoaxes/joystick.h | 15 + firmware/octoaxes/objectives.cpp | 200 +++ firmware/octoaxes/objectives.h | 32 + firmware/octoaxes/octoaxes.ino | 199 +++ firmware/octoaxes/platformio.ini | 195 +++ firmware/octoaxes/serial.cpp | 666 ++++++++ firmware/octoaxes/serial.h | 82 + firmware/octoaxes/stepaxis.cpp | 251 +++ firmware/octoaxes/stepaxis.h | 36 + firmware/octoaxes/tmc/hal/TMC_SPI.cpp | 240 +++ firmware/octoaxes/tmc/hal/TMC_SPI.h | 175 ++ firmware/octoaxes/tmc/helpers/API_Header.h | 40 + firmware/octoaxes/tmc/helpers/Bits.h | 85 + firmware/octoaxes/tmc/helpers/CRC.c | 211 +++ firmware/octoaxes/tmc/helpers/CRC.h | 23 + firmware/octoaxes/tmc/helpers/Config.h | 39 + firmware/octoaxes/tmc/helpers/Constants.h | 22 + firmware/octoaxes/tmc/helpers/Functions.c | 170 ++ firmware/octoaxes/tmc/helpers/Functions.h | 18 + firmware/octoaxes/tmc/helpers/Macros.h | 55 + .../octoaxes/tmc/helpers/RegisterAccess.h | 83 + firmware/octoaxes/tmc/helpers/Types.h | 100 ++ firmware/octoaxes/tmc/ic/TMC2240/TMC2240.cpp | 205 +++ firmware/octoaxes/tmc/ic/TMC2240/TMC2240.h | 225 +++ .../tmc/ic/TMC2240/TMC2240_HW_Abstraction.h | 567 +++++++ firmware/octoaxes/tmc/ic/TMC2660/TMC2660.cpp | 385 +++++ firmware/octoaxes/tmc/ic/TMC2660/TMC2660.h | 322 ++++ .../tmc/ic/TMC2660/TMC2660_HW_Abstraction.h | 298 ++++ .../octoaxes/tmc/ic/TMC4361A/TMC4361A.cpp | 272 ++++ firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A.h | 287 ++++ .../tmc/ic/TMC4361A/TMC4361A_HW_Abstraction.h | 1416 +++++++++++++++++ firmware/octoaxes/tmc/motion/MotorControl.cpp | 1405 ++++++++++++++++ firmware/octoaxes/tmc/motion/MotorControl.h | 559 +++++++ firmware/octoaxes/trigger.cpp | 127 ++ firmware/octoaxes/trigger.h | 59 + firmware/octoaxes/utils.cpp | 32 + firmware/octoaxes/utils.h | 6 + 53 files changed, 13538 insertions(+) create mode 100644 firmware/octoaxes/.gitignore create mode 100644 firmware/octoaxes/axesmrg.cpp create mode 100644 firmware/octoaxes/axesmrg.h create mode 100644 firmware/octoaxes/axis.cpp create mode 100644 firmware/octoaxes/axis.h create mode 100644 firmware/octoaxes/build_opt.h create mode 100644 firmware/octoaxes/commandprocessor.cpp create mode 100644 firmware/octoaxes/commandprocessor.h create mode 100644 firmware/octoaxes/config.h create mode 100644 firmware/octoaxes/def_octopi_80120.h create mode 100755 firmware/octoaxes/download.sh create mode 100644 firmware/octoaxes/filterwheel.cpp create mode 100644 firmware/octoaxes/filterwheel.h create mode 100644 firmware/octoaxes/illumination.cpp create mode 100644 firmware/octoaxes/illumination.h create mode 100644 firmware/octoaxes/joystick.cpp create mode 100644 firmware/octoaxes/joystick.h create mode 100644 firmware/octoaxes/objectives.cpp create mode 100644 firmware/octoaxes/objectives.h create mode 100644 firmware/octoaxes/octoaxes.ino create mode 100644 firmware/octoaxes/platformio.ini create mode 100644 firmware/octoaxes/serial.cpp create mode 100644 firmware/octoaxes/serial.h create mode 100644 firmware/octoaxes/stepaxis.cpp create mode 100644 firmware/octoaxes/stepaxis.h create mode 100644 firmware/octoaxes/tmc/hal/TMC_SPI.cpp create mode 100644 firmware/octoaxes/tmc/hal/TMC_SPI.h create mode 100644 firmware/octoaxes/tmc/helpers/API_Header.h create mode 100644 firmware/octoaxes/tmc/helpers/Bits.h create mode 100644 firmware/octoaxes/tmc/helpers/CRC.c create mode 100644 firmware/octoaxes/tmc/helpers/CRC.h create mode 100644 firmware/octoaxes/tmc/helpers/Config.h create mode 100644 firmware/octoaxes/tmc/helpers/Constants.h create mode 100644 firmware/octoaxes/tmc/helpers/Functions.c create mode 100644 firmware/octoaxes/tmc/helpers/Functions.h create mode 100644 firmware/octoaxes/tmc/helpers/Macros.h create mode 100644 firmware/octoaxes/tmc/helpers/RegisterAccess.h create mode 100644 firmware/octoaxes/tmc/helpers/Types.h create mode 100644 firmware/octoaxes/tmc/ic/TMC2240/TMC2240.cpp create mode 100644 firmware/octoaxes/tmc/ic/TMC2240/TMC2240.h create mode 100644 firmware/octoaxes/tmc/ic/TMC2240/TMC2240_HW_Abstraction.h create mode 100644 firmware/octoaxes/tmc/ic/TMC2660/TMC2660.cpp create mode 100644 firmware/octoaxes/tmc/ic/TMC2660/TMC2660.h create mode 100644 firmware/octoaxes/tmc/ic/TMC2660/TMC2660_HW_Abstraction.h create mode 100644 firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A.cpp create mode 100644 firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A.h create mode 100644 firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A_HW_Abstraction.h create mode 100644 firmware/octoaxes/tmc/motion/MotorControl.cpp create mode 100644 firmware/octoaxes/tmc/motion/MotorControl.h create mode 100644 firmware/octoaxes/trigger.cpp create mode 100644 firmware/octoaxes/trigger.h create mode 100644 firmware/octoaxes/utils.cpp create mode 100644 firmware/octoaxes/utils.h diff --git a/firmware/octoaxes/.gitignore b/firmware/octoaxes/.gitignore new file mode 100644 index 000000000..147c4b906 --- /dev/null +++ b/firmware/octoaxes/.gitignore @@ -0,0 +1,2 @@ +.pio +*.json diff --git a/firmware/octoaxes/axesmrg.cpp b/firmware/octoaxes/axesmrg.cpp new file mode 100644 index 000000000..7b2fe5d23 --- /dev/null +++ b/firmware/octoaxes/axesmrg.cpp @@ -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; +} diff --git a/firmware/octoaxes/axesmrg.h b/firmware/octoaxes/axesmrg.h new file mode 100644 index 000000000..9c342ba4a --- /dev/null +++ b/firmware/octoaxes/axesmrg.h @@ -0,0 +1,42 @@ +#ifndef AXES_MANAGER_H +#define AXES_MANAGER_H + +#include +#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 diff --git a/firmware/octoaxes/axis.cpp b/firmware/octoaxes/axis.cpp new file mode 100644 index 000000000..75ea210f1 --- /dev/null +++ b/firmware/octoaxes/axis.cpp @@ -0,0 +1,1323 @@ +#include "axis.h" +#include "build_opt.h" +#include "tmc/ic/TMC4361A/TMC4361A.h" +#include + +static inline int sgn(int val) { + if (val < 0) + return -1; + if (val == 0) + return 0; + return 1; +} + +// Constructor +Axis::Axis(uint8_t csPin, uint8_t axisIndex, const char *axisName) + : _csPin(csPin), _axisIndex(axisIndex), _axisName(axisName) { + + _currentState = STATE_IDLE; + _previousState = STATE_IDLE; + _stateStartTime = 0; + _homeFound = false; + + _maxVelocityMicrosteps = 0; + _maxAccelerationMicrosteps = 0; + + // New architecture: use axisIndex as the IC identifier + _icID = axisIndex; + + // Added: initialize state-change detection + _lastReportedState = STATE_IDLE; + _stateChanged = false; + _lastStateReportTime = 0; + + // Added: initialize movement state + _isMoving = false; + _moveDirection = 0; + _softLimitsEnabled = false; + _needReenableLimits = false; + + // Initialize the config struct (value-init: equivalent to zeroing + respects the member default initializer polarityAffectsChip=false. + // AxisConfig now has default member initializers making it non-trivial, so memset can no longer be used, otherwise a -Wclass-memaccess warning) + _config = AxisConfig{}; +} + +// Initialization function +bool Axis::begin(const AxisConfig &config) { + _config = config; + + // HOME timeout ms + _homing_timeout_ms = _config.homing_timeout_ms; + + // Configure the CS pin +#ifndef USE_HC154_CS + // octoaxes direct GPIO CS: _csPin is the Teensy physical pin number, configured as OUTPUT default HIGH (not selected) + pinMode(_csPin, OUTPUT); + digitalWrite(_csPin, HIGH); +#endif + // USE_HC154_CS (octoaxesplus): _csPin is the HC154 channel number (0-15), not a GPIO pin number. + // The physical chip-select is initialized by tmc_spi_init() and, at the transaction level by tmc4361A_readWriteSPI(), + // switched via Pins::hc154_select(). Calling pinMode/digitalWrite(_csPin) here would + // wrongly drive Teensy physical pins 8/9/10 (on squid++ these are CAMERA_TRIGGER_2 / + // CAMERA_TRIGGER_1 / ILLUMINATION_D8), causing the camera and laser to be triggered by mistake during init. + + // ========== New-architecture initialization ========== + // Set the driver type (when DRIVER_AUTO, auto-detected by motor_initMotionController) + motorParams[_icID].driverType = _config.driverType; + + // Initialize the motion-parameter cache (used for unit conversion in the new API) + MotionConfig motionConfig = { + .clockFrequency = _config.clockFrequency, + .screwPitchMM = _config.screwPitchMM, + .fullStepsPerRev = (uint16_t)_config.fullStepsPerRev, + .microsteps = (uint16_t)_config.microstepping, + .maxVelocityMM = _config.maxVelocityMM, + .maxAccelerationMM = _config.maxAccelerationMM, + .maxDecelerationMM = _config.maxAccelerationMM, + .useSShapedRamp = _config.useSShapedRamp, + .astartMM = _config.astartMM, + .dfinalMM = _config.dfinalMM, + .bow1 = 0, + .bow2 = 0, + .bow3 = 0, + .bow4 = 0}; + // motor_initMotionController returns false when TMC4361A SPI communication fails (after writing + // SW_RESET, reading VERSION_NO returns 0 or -1). Check the return value and propagate the failure upward so + // beginAll() can record which axis chip failed to come up, avoiding later operations on an uninitialized chip. + if (!motor_initMotionController(_icID, &motionConfig)) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":BEGIN_FAIL motor_initMotionController (TMC4361A SPI no response)"); + return false; + } + + // After auto-detection completes, write back the actual driver type + if (_config.driverType == DRIVER_AUTO) { + _config.driverType = motorParams[_icID].driverType; + } + + // Initialize the driver configuration + MotorConfig motorConfig = { + .driverType = _config.driverType, + .rSense = _config.r_sense, + .runCurrentMA = _config.motorCurrentMA, + .holdCurrentRatio = _config.holdCurrent, + .microstepRes = 0, // 256 microsteps + .interpolation = true, + .toff = 3, // TOFF = 3 + .hstrt = 0, // HSTRT = 0 (matches legacy Squid CHOPCONF=0x000900C3, zero-hysteresis quiet) + .hend = 0, // HEND register value = 3, actual value = 0 (matches legacy Squid) + .tbl = 2, // TBL = 2 + .stallThreshold = (int8_t)_config.stallSensitivity, + .stallFilter = true, + .enableStealthChop = false, + .globalScaler = 0, // full scale (256) + .iholdDelay = 7, + .currentRange = _config.currentRange}; + motor_initDriver(_icID, &motorConfig); + + // Configure the limit switches + LimitConfig limitConfig = { + .enableLeft = _config.enableLeftLimitSwitch, + .enableRight = _config.enableRightLimitSwitch, + .leftPolarity = _config.leftSwitchPolarity, + .rightPolarity = _config.rightSwitchPolarity, + .leftFlipped = _config.leftFlipped, + .rightFlipped = _config.rightFlipped, + .homingSwitch = _config.homingSwitch, + .homeSafetyMarginMM = _config.homeSafetyMarginMM}; + motor_configLimitSwitches(_icID, &limitConfig); + + // Set the motion parameters + setMotionParameters(_config.maxVelocityMM, _config.maxAccelerationMM); + + // Enable the homing limit + motor_enableHomingLimit(_icID, _config.rightSwitchPolarity, + _config.homingSwitch, + mmToMicrosteps(_config.homeSafetyMarginMM)); + + // Disable the virtual limit switches (initial state) + enableSoftLimits(false); + + // Encoder initialization + if (_config.enableEncoder && _config.encoderLinesPerRev > 0) { + uint32_t transitions = (uint32_t)_config.encoderLinesPerRev; + motor_initABNEncoder(_icID, transitions, + 32, // filter_wait_time + 4, // filter_exponent + 512, // filter_vmean + _config.invertEncoderDir); + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":ENCODER_INIT lines="); + DEBUG_PRINT(_config.encoderLinesPerRev); + DEBUG_PRINT(" transitions="); + DEBUG_PRINTLN(transitions); + } + + // Disable PID (using the new API) + motor_disablePID(_icID); + + // Configure StallGuard (using the new API) + // TMC2660 SG2: SGT=12 has been stable over long-term testing; normal motion does not false-trigger, a collision stops the motor. + // TMC2240 SG4: the algorithm is incompatible with SG2; SGT=12 very easily false-triggers the ACTIVE_STALL_F latch + // locking up the chip (diagnosed on-site 2026-05-12 with legacy Squid X stuck, STATUS bit11 latched; + // once triggered, power must be cut and USB unplugged to reset). The existing VSTOP + // recovery path of motor_moveToMicrosteps does not clear this latch (it only clears VSTOPL/R_ACTIVE_F bit9/10). + // Temporary workaround: skip enabling stall on TMC2240; keep config.enableStallSensitivity / + // stallSensitivity parameters for future SG4 tuning, to enable after the chip-level latch recovery is fixed. + if (_config.enableStallSensitivity && _config.driverType != DRIVER_TMC2240) + motor_configStallGuard(_icID, _config.stallSensitivity, true, true); + + // Enable the axis by default + enableAxis(); + + return true; +} + +// Set motion parameters (using the new API) +void Axis::setMotionParameters(float maxVelocityMM, float maxAccelerationMM) { + _maxVelocityMicrosteps = motor_velocityMMToInternal(_icID, maxVelocityMM); + _maxAccelerationMicrosteps = motor_accelMMToInternal(_icID, maxAccelerationMM); + + motor_setMaxVelocity(_icID, maxVelocityMM); + motor_setMaxAcceleration(_icID, maxAccelerationMM); +} + +// State-machine update +void Axis::update() { + // Save the old state for comparison + AxisState oldState = _currentState; + + switch (_currentState) { + case STATE_HOMING_INIT: + case STATE_HOMING_SEARCH: + case STATE_HOMING_SET_ZERO: + performHomingSequence(); + break; + + case STATE_LEAVING_HOME: + performLeavingHome(); + break; + + case STATE_MOVING: { + checkMovementComplete(); + + // Limit-state check: matches legacy Squid `check_limits` 10ms throttle (operations.cpp:533) + // reduces SPI bus contention; the hard-limit completion check tolerates a 0-10ms delay (the chip has already physically stopped) + // (#5, 2026-05-19) + if (_limitCheckThrottle >= 10000) { + _limitCheckThrottle = 0; + checkLimitPosition(); + } + + // Delayed re-enable of the virtual limits after VSTOP recovery: + // only re-enable the limits after the motor leaves the boundary (VSTOP flags cleared in STATUS), + // to avoid immediately re-triggering VSTOP at the boundary. + if (_needReenableLimits) { + uint32_t st = motor_readStatus(_icID); + bool vstopStillActive = (st & TMC4361A_VSTOPL_ACTIVE_F_MASK) || + (st & TMC4361A_VSTOPR_ACTIVE_F_MASK); + if (!vstopStillActive) { + motor_enableSoftLimits(_icID, true, true); + _needReenableLimits = false; + } + } + + // Timeout check while moving + if (checkTimeout(MOVEMENT_TIMEOUT_MS)) { + handleError("Movement timeout"); + } + } break; + + case STATE_IDLE: + // The idle state needs no special handling + break; + + case STATE_ERROR: + // The error state requires external intervention + break; + } + + // Check whether the state changed + if (oldState != _currentState) { + _stateChanged = true; + } + + // Report the state change (if needed) + reportStateIfChanged(); +} + +// Added: state-report function +void Axis::reportStateIfChanged(bool force) { + // Check whether a report is needed + bool shouldReport = false; + + if (force) { + // Force a report + shouldReport = true; + } else if (_stateChanged) { + // The state changed + shouldReport = true; + } else if (_currentState == STATE_MOVING) { + } else if (_currentState == STATE_HOMING_INIT || + _currentState == STATE_HOMING_SEARCH || + _currentState == STATE_HOMING_SET_ZERO || + _currentState == STATE_LEAVING_HOME) { + } else { + } + + if (shouldReport) { + handleEmergency(); + _stateChanged = false; + _lastStateReportTime = millis(); + _lastReportedState = _currentState; + } +} + +// Limit-position handler +void Axis::checkLimitPosition() { + uint32_t event = readAxisEvent(); + + // Virtual limits (software limits): trust that the upper-layer isMoveAllowedByDirection() has guaranteed + // the in-progress move goes toward the safer direction; VSTOP_ACTIVE during this time is a + // sticky/residual state left by the chip after SET_LIM placed the motor in the forbidden zone, and should not be treated as a real out-of-bounds. + // motor_moveToMicrosteps() has temporarily cleared VIRT_*_LIMIT_EN so the motor can move; + // the completion check is handled by checkMovementComplete() (XACTUAL == XTARGET). + uint32_t vstop_bits = + event & (TMC4361A_VSTOPL_ACTIVE_MASK | TMC4361A_VSTOPR_ACTIVE_MASK); + if (vstop_bits) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":VSTOP active during move (ignored, gate handled upstream): event=0x"); + DEBUG_PRINTLNF(event, HEX); + // do not call completeMovement(); let checkMovementComplete() finish normally when XACTUAL reaches XTARGET + } + + // Hardware limits (keep the direction check; hardware limits require a direction match) + uint32_t hw_datagram = event & (TMC4361A_STOPL_EVENT_MASK | TMC4361A_STOPR_EVENT_MASK); + hw_datagram >>= TMC4361A_STOPL_EVENT_SHIFT; + uint8_t hw_result = hw_datagram & 0xff; + + if ((hw_result == RGHT_SW && _moveDirection == RGHT_DIR) || + (hw_result == LEFT_SW && _moveDirection == LEFT_DIR)) { + DEBUG_PRINT("Hardware Limit Stop: "); + DEBUG_PRINTLN(hw_result); + completeMovement(); + return; + } + + // Determine whether this is a stall state + if (event & 0x20000000) { + DEBUG_PRINTLN("Axis Is Stop for Stalling"); + DEBUG_PRINTLNF(event, HEX); + } else { + if (event != 0) { + DEBUG_PRINT("Axis Event is not Zero: "); + DEBUG_PRINTLNF(event, HEX); + } + } +} + +// Command processing +bool Axis::processCommand(const String &command) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":CMD_RECV:"); + DEBUG_PRINTLN(command); // debug point 0 - command received + + if (command.startsWith("GET_POSITION")) { + return handleGetPosition(); + } else if (command.startsWith("SET_LIMITS")) { + return handleSetLimits(command); + } else if (command.startsWith("MOVE_AXIS")) { + return handleMoveAxis(command); + } else if (command.startsWith("MOVETO_AXIS")) { + return handleMoveToAxis(command); + } else if (command.startsWith("HOMING")) { + return handleHoming(); + } else if (command.startsWith("GET_DATA")) { + return handleGetData(); + } else if (command.startsWith("DISABLE")) { + return handleAxisAbilityToggle(false); + } else if (command.startsWith("ENABLE")) { + return handleAxisAbilityToggle(true); + } else if (command.startsWith("RESET")) { + return handleReset(); + } else if (command.startsWith("DEBUG_REG")) { + return handleDebugReg(); + } else { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Unknown command: "); + DEBUG_PRINTLN(command); + return false; + } +} + +// Command-handling helper method +bool Axis::handleGetPosition() { + int32_t microsteps = getCurrentPosition(); + [[maybe_unused]] float positionMM = microstepsToMM(microsteps); + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Current Position (mm):"); + DEBUG_PRINTLNF(positionMM, 3); // increase display precision + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Current Position (microsteps):"); + DEBUG_PRINTLN(microsteps); + return true; +} + +int32_t Axis::hexStringToInt32(String hex) { + char *endptr; + uint32_t value = strtoul(hex.c_str(), &endptr, 16); + return (int32_t)value; +} + +bool Axis::moveAxis(int32_t value) { + _cmdRecvMicros = micros(); + _moveDirection = sgn(value); + + if (!moveRelativeMicrosteps(value)) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":MOVE_AXIS ERROR: Movement failed"); + return false; + } else { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":MOVE_AXIS (usteps): "); + DEBUG_PRINTLN(value); + } + return true; +} + +bool Axis::handleMoveAxis(const String &command) { + int space1 = command.indexOf(' '); + int space2 = command.indexOf(' ', space1 + 1); + + if (space1 == -1 || space2 == -1) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":MOVE_AXIS ERROR: Invalid format"); + return false; + } + String dataType = command.substring(space1 + 1, space2); + String hexData = command.substring(space2 + 1); + + int32_t value = hexStringToInt32(hexData); + + return moveAxis(value); +} + +bool Axis::handleMoveToAxis(const String &command) { + int space1 = command.indexOf(' '); + int space2 = command.indexOf(' ', space1 + 1); + + if (space1 == -1 || space2 == -1) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":MOVETO_AXIS ERROR: Invalid format"); + return false; + } + String dataType = command.substring(space1 + 1, space2); + String hexData = command.substring(space2 + 1); + + int32_t value = hexStringToInt32(hexData); + _moveDirection = sgn(value); + + if (!moveToPositionMicrosteps(value)) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":MOVETO_AXIS ERROR: Movement failed"); + return false; + } + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":MOVETO_AXIS (usteps): "); + DEBUG_PRINTLN(value); + return true; +} + +// Added: movement-detection function (using the new API) +void Axis::checkMovementComplete() { + if (!_isMoving) + return; + + // Use the chip STATUS.TARGET_REACHED_F bit instead of read-XACTUAL + read-XTARGET-and-compare. + // The chip updates this bit in real time after XTARGET is written (set to 1 when XACTUAL == XTARGET, + // and EVENTS were cleared after motor_moveToMicrosteps to prevent sticky residue), so no waiting is needed. + // This reduces the completion-check path from 2 SPI reads to 1, and is more reliable (the chip's authoritative signal). + if (motor_isTargetReached(_icID)) { + completeMovement(); + } +} + +// Added: start movement (using the new API) +void Axis::startMovement() { + _isMoving = true; + _moveStartMicros = micros(); + setState(STATE_MOVING); +} + +// Added: complete movement +void Axis::completeMovement() { + _isMoving = false; + setState(STATE_IDLE); + + // Restore the virtual limits when the move completes (if the recovery path disabled the limits and the update loop did not restore them in time) + if (_needReenableLimits && _softLimitsEnabled) { + motor_enableSoftLimits(_icID, true, true); + _needReenableLimits = false; + } + +#ifdef ENABLE_DEBUG + // DEBUG-only: during debugging, log motor / prep / total time and position vs target + // Production builds (NDEBUG) do not read SPI, saving ~200us/move x 1000 ~= 200ms (#3, 2026-05-19) + unsigned long now = micros(); + unsigned long motorTime = now - _moveStartMicros; + unsigned long totalTime = now - _cmdRecvMicros; + unsigned long prepTime = _moveStartMicros - _cmdRecvMicros; + int32_t endPos = motor_getPositionMicrosteps(_icID); + int32_t targetPos = motor_getTargetMicrosteps(_icID); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":DONE: total="); + DEBUG_PRINT(totalTime); + DEBUG_PRINT("us prep="); + DEBUG_PRINT(prepTime); + DEBUG_PRINT("us motor="); + DEBUG_PRINT(motorTime); + DEBUG_PRINT("us pos="); + DEBUG_PRINT(endPos); + DEBUG_PRINT(" tgt="); + DEBUG_PRINT(targetPos); + DEBUG_PRINT(" err="); + DEBUG_PRINTLN(endPos - targetPos); +#endif +} + +bool Axis::handleHoming() { + if (!startHoming()) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":HOMING ERROR: Already in progress or busy"); + return false; + } + + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Received HOME command, starting homing process..."); + return true; +} + +bool Axis::handleReset() { + _isMoving = false; + + // Restore microstepping (may have been changed during homing) + restoreNormalMicrosteps(); + + // Clear state + readLimitSwitches(); + readSwitchEvent(); + + // Reset RAMPMODE (may become HOLD mode after a hardware limit triggers) + motor_resetRampMode(_icID); + + // Restore motion parameters (VMAX/AMAX may have been zeroed by stop) + setMotionParameters(_config.maxVelocityMM, _config.maxAccelerationMM); + + setState(STATE_IDLE); + + // Report the state immediately + reportStateIfChanged(true); + + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Received RESET command, starting reset process..."); + return true; +} + +// Move to an absolute position (microstep units, protocol-layer entry point) +bool Axis::moveToPositionMicrosteps(int32_t targetMicrosteps) { + // 2026-05-25 hardware direction inversion: mirror-assembled hardware needs the chip to move in the opposite direction + // the host protocol layer is unchanged (still sends commands per the "standard Squid design"); the firmware layer inverts the target + if (_config.invert_direction) { + targetMicrosteps = -targetMicrosteps; + } + + // Auto-recover from the error state (non-hardware faults such as a virtual-limit timeout) + if (_currentState == STATE_ERROR) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Auto-recovery from error state"); + handleReset(); + } + + // STATE_IDLE: normal path + // STATE_MOVING: mimics legacy Squid (main_controller_teensy41.ino:900 MOVETO_X handler has no busy check) + // -- overwrites the chip XTARGET; the chip ramp generator smoothly switches the target. + // STATE_HOMING_*/LEAVING_HOME: during homing the chip is in velocity mode, so overwriting would break homing, + // so still reject (this is an explicit rejection; the caller should wait until homing completes before sending). + if (_currentState != STATE_IDLE && _currentState != STATE_MOVING) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Movement rejected: Axis is homing, current state: "); + DEBUG_PRINTLN(_currentState); + return false; + } + + if (!isWithinSoftLimits(targetMicrosteps)) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Movement rejected: Outside soft limits"); + return false; + } + + // Direction-aware clamp: a target toward the forbidden zone is clamped to the boundary, so the motor stops at the boundary + // (compatible with legacy Squid behavior: the legacy Squid host cannot be changed, so the firmware must handle out-of-bounds targets as a fallback) + targetMicrosteps = clampTargetByDirection(targetMicrosteps); + + // No-op short-circuit: when after clamping target == current position the motor need not move, + // skip motor_moveToMicrosteps + startMovement and return directly, + // to avoid _isMoving being set wrongly, which would make the host receive IN_PROGRESS and wait 5 seconds until timeout. + // Typical case: the motor is already stuck at the limit boundary and the host keeps sending out-of-bounds MOVE commands. + int32_t currentPos = motor_getPositionMicrosteps(_icID); + if (targetMicrosteps == currentPos) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Move no-op (clamped to current position), skipping motor command"); + return true; + } + + // motor_moveToMicrosteps already reads STATUS to check VSTOP internally; reuse its return value + // to avoid a redundant SPI read (2026-05-18 acquisition optimization #2.2, saves ~10-20us/move) + bool vstopWasActive = motor_moveToMicrosteps(_icID, targetMicrosteps); + startMovement(); // set the movement state + + if (vstopWasActive && _softLimitsEnabled) { + _needReenableLimits = true; + } + + return true; +} + +// Move to an absolute position (mm units, thin wrapper) +bool Axis::moveToPosition(float positionMM) { + if (!isValidPosition(positionMM)) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Movement rejected: Invalid position"); + DEBUG_PRINTLN(positionMM); + return false; + } + return moveToPositionMicrosteps(motor_mmToMicrosteps(_icID, positionMM)); +} + +// Relative move (microstep units, protocol-layer entry point) +bool Axis::moveRelativeMicrosteps(int32_t deltaMicrosteps) { + // 2026-05-25 hardware direction inversion: invert delta so the chip moves in the opposite physical direction + if (_config.invert_direction) { + deltaMicrosteps = -deltaMicrosteps; + } + + // Auto-recover from the error state + if (_currentState == STATE_ERROR) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Auto-recovery from error state"); + handleReset(); + } + + // STATE_IDLE: normal path + // STATE_MOVING: mimics legacy Squid (main_controller_teensy41.ino:845 MOVE_X handler has no busy check) + // -- recompute the target from the chip's current position and overwrite XTARGET. Semantics match legacy Squid: + // delta is relative to "the chip current position when the command arrives", not "the target of the previous command". + // STATE_HOMING_*/LEAVING_HOME: reject (same as moveToPositionMicrosteps). + if (_currentState != STATE_IDLE && _currentState != STATE_MOVING) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Move rejected: Axis is homing, current state: "); + DEBUG_PRINTLN(_currentState); + return false; + } + + int32_t currentPos = motor_getPositionMicrosteps(_icID); + int32_t targetPos = currentPos + deltaMicrosteps; + + if (!isWithinSoftLimits(targetPos)) { + return false; + } + + // Direction-aware clamp: a target toward the forbidden zone is clamped to the boundary, so the motor stops at the boundary + // (compatible with legacy Squid behavior: the legacy Squid host cannot be changed, so the firmware must handle out-of-bounds targets as a fallback) + targetPos = clampTargetByDirection(targetPos); + + // No-op short-circuit: when after clamping target == current position, skip motor + startMovement, + // to avoid _isMoving being set wrongly, making the host wait a full 5 seconds for timeout (see the corresponding comment in moveToPositionMicrosteps) + if (targetPos == currentPos) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Move no-op (clamped to current position), skipping motor command"); + return true; + } + + // motor_moveToMicrosteps already reads STATUS to check VSTOP internally; reuse its return value + // to avoid a redundant SPI read (2026-05-18 acquisition optimization #2.2, saves ~10-20us/move) + bool vstopWasActive = motor_moveToMicrosteps(_icID, targetPos); + startMovement(); // set the movement state + + if (vstopWasActive && _softLimitsEnabled) { + _needReenableLimits = true; + } + + return true; +} + +// Relative move (mm units, thin wrapper) +bool Axis::moveRelative(float distanceMM) { + return moveRelativeMicrosteps(motor_mmToMicrosteps(_icID, distanceMM)); +} + +// Set speed +void Axis::setSpeed(float speedMM) { + motor_setMaxVelocity(_icID, speedMM); +} + +// Smooth stop +void Axis::smoothStop() { + motor_stop(_icID); + completeMovement(); // clear the movement state +} + +// Motion-control functions +void Axis::disableAxis() { + motor_enableDriver(_icID, false); + _isEnabled = false; // update the enable state +} + +void Axis::enableAxis() { + motor_enableDriver(_icID, true); + _isEnabled = true; // update the enable state +} + +// Set the current position +void Axis::setCurrentPosition(float positionMM) { + motor_setCurrentPosition(_icID, positionMM); +} + +// Get the current position in microsteps (using the new API) +int32_t Axis::getCurrentPosition() const { + return motor_getPositionMicrosteps(_icID); +} + +// Get the current position (mm) (using the new API) +float Axis::getCurrentPositionMM() const { + return motor_getPositionMM(_icID); +} + +// Get the current position (microsteps) +// When the encoder is enabled, return ENC_POS (converted via ENC_CONST, same units as microsteps) +// When disabled, return XACTUAL (open-loop position) +// 2026-05-25 hardware direction inversion: invert the value reported to the host so it sees a direction consistent with the protocol layer +int32_t Axis::getCurrentPositionMicrosteps() const { + int32_t raw; + if (_config.enableEncoder) { + raw = (int32_t)tmc4361A_readRegister(_icID, TMC4361A_ENC_POS); + } else { + raw = motor_getPositionMicrosteps(_icID); + } + return _config.invert_direction ? -raw : raw; +} + +// Get the encoder position (microstep units, converted via ENC_CONST) +// When the encoder is not enabled, return XACTUAL +int32_t Axis::getEncoderPositionMicrosteps() const { + int32_t raw; + if (_config.enableEncoder) { + raw = (int32_t)tmc4361A_readRegister(_icID, TMC4361A_ENC_POS); + } else { + raw = motor_getPositionMicrosteps(_icID); + } + return _config.invert_direction ? -raw : raw; +} + +// Homing microstepping switch +void Axis::switchToHomingMicrosteps() { + if (_config.homingMicrostepping != _config.microstepping) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Switch microsteps for homing: "); + DEBUG_PRINT(_config.microstepping); + DEBUG_PRINT(" -> "); + DEBUG_PRINTLN(_config.homingMicrostepping); + motor_setMicrosteps(_icID, _config.homingMicrostepping); + setMotionParameters(_config.maxVelocityMM, _config.maxAccelerationMM); + } +} + +void Axis::restoreNormalMicrosteps() { + if (_config.homingMicrostepping != _config.microstepping) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Restore microsteps after homing: "); + DEBUG_PRINT(_config.homingMicrostepping); + DEBUG_PRINT(" -> "); + DEBUG_PRINTLN(_config.microstepping); + motor_setMicrosteps(_icID, _config.microstepping); + setMotionParameters(_config.maxVelocityMM, _config.maxAccelerationMM); + } +} + +// Start homing +bool Axis::startHoming() { + // Auto-recover from the error state + if (_currentState == STATE_ERROR) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Auto-recovery from error state for homing"); + handleReset(); + } + + if (_currentState != STATE_IDLE) { + return false; + } + + setState(STATE_HOMING_INIT); + return true; +} + +// Check whether homing is in progress +bool Axis::isHomingInProgress() const { + return _currentState == STATE_HOMING_INIT || + _currentState == STATE_HOMING_SEARCH || + _currentState == STATE_HOMING_SET_ZERO || + _currentState == STATE_LEAVING_HOME; +} + +// Check whether the movement is complete (using the new API) +// Dual condition: position reached the target + VACTUAL is zero (the motor has actually stopped) +// prevents reporting completion too early when, during the S-ramp deceleration phase, XACTUAL briefly equals XTARGET but the speed has not reached zero +bool Axis::isMovementComplete() const { + return motor_getPositionMicrosteps(_icID) == motor_getTargetMicrosteps(_icID) + && !motor_isRunning(_icID); +} + +// Set soft limits (using the new API) +void Axis::setSoftLimits(float lowerLimitMM, float upperLimitMM) { + int32_t lowerMicrosteps = motor_mmToMicrosteps(_icID, lowerLimitMM); + int32_t upperMicrosteps = motor_mmToMicrosteps(_icID, upperLimitMM); + + motor_setSoftLimits(_icID, lowerMicrosteps, upperMicrosteps); + + // sync the direction-gate shadow (both sides) + _softLimits.leftEnabled = true; + _softLimits.leftValue = lowerMicrosteps; + _softLimits.rightEnabled = true; + _softLimits.rightValue = upperMicrosteps; + + enableSoftLimits(true); +} + +// Enable/disable soft limits (using the new API) +void Axis::enableSoftLimits(bool enable) { + motor_enableSoftLimits(_icID, enable, enable); + _softLimitsEnabled = enable; + if (!enable) { + // when explicitly disabling soft limits, clear the direction-gate shadow to allow movement in any direction + _softLimits.leftEnabled = false; + _softLimits.rightEnabled = false; + } +} + +// Set one-sided soft limit (direction: +1=upper/right, -1=lower/left) +void Axis::setOneSoftLimit(int direction, int32_t valueMicrosteps) { + // first set XTARGET to the current position to prevent the motor from auto-resuming motion after the limit is loosened + int32_t xactual = tmc4361A_readRegister(_icID, TMC4361A_XACTUAL); + tmc4361A_writeRegister(_icID, TMC4361A_XTARGET, xactual); + + uint32_t refConf = tmc4361A_readRegister(_icID, TMC4361A_REFERENCE_CONF); + if (direction > 0) { + tmc4361A_writeRegister(_icID, TMC4361A_VIRT_STOP_RIGHT, valueMicrosteps); + refConf |= TMC4361A_VIRTUAL_RIGHT_LIMIT_EN_MASK; + refConf |= (1 << TMC4361A_VIRT_STOP_MODE_SHIFT); + _softLimits.rightEnabled = true; + _softLimits.rightValue = valueMicrosteps; + } else { + tmc4361A_writeRegister(_icID, TMC4361A_VIRT_STOP_LEFT, valueMicrosteps); + refConf |= TMC4361A_VIRTUAL_LEFT_LIMIT_EN_MASK; + refConf |= (1 << TMC4361A_VIRT_STOP_MODE_SHIFT); + _softLimits.leftEnabled = true; + _softLimits.leftValue = valueMicrosteps; + } + tmc4361A_writeRegister(_icID, TMC4361A_REFERENCE_CONF, refConf); + _softLimitsEnabled = true; +} + +// Direction-aware clamp: see the comment in axis.h +// +// Boundary margin (BOUNDARY_MARGIN) prevents the chip ramp generator's insufficient deceleration precision from causing a hard-stop latch: +// Measured case (main_hcs.log 2026-05-09 10:31:57, cmd 37 MOVETO_X usteps=6300 = L+1): +// host target=L+1 (5mm = 6300, exactly 1 microstep against the X_NEG_LIMIT=6299 boundary), +// the chip writes XTARGET and starts ramp deceleration; sub-microstep precision lets the ramp briefly cross L -> triggering +// VSTOPL_ACTIVE and entering a hard-stop latch, **so all subsequent MOVE_X in any direction +// cannot start a ramp** (the chip's internal latch does not release; clearing EVENTS alone cannot unlock it). +// in the safe zone, force the target at least N microsteps away from the boundary to avoid this quirk. +static constexpr int32_t BOUNDARY_MARGIN_MICROSTEPS = 100; + +int32_t Axis::clampTargetByDirection(int32_t target) const { + int32_t C = motor_getPositionMicrosteps(_icID); + int32_t original = target; + if (_softLimits.leftEnabled) { + int32_t L = _softLimits.leftValue; + // when out of bounds, lower bound = C (forbid going further down); in the safe zone, lower bound = L + margin (prevents the ramp from crossing) + int32_t effective_lower = (C <= L) ? C : (L + BOUNDARY_MARGIN_MICROSTEPS); + if (target < effective_lower) target = effective_lower; + } + if (_softLimits.rightEnabled) { + int32_t R = _softLimits.rightValue; + int32_t effective_upper = (C >= R) ? C : (R - BOUNDARY_MARGIN_MICROSTEPS); + if (target > effective_upper) target = effective_upper; + } + if (target != original) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Move clamped (soft limit): target="); + DEBUG_PRINT(original); + DEBUG_PRINT(" → "); + DEBUG_PRINT(target); + DEBUG_PRINT(" (C="); + DEBUG_PRINT(C); + DEBUG_PRINT(" L="); + DEBUG_PRINT(_softLimits.leftEnabled ? _softLimits.leftValue : 0); + DEBUG_PRINT(" R="); + DEBUG_PRINT(_softLimits.rightEnabled ? _softLimits.rightValue : 0); + DEBUG_PRINTLN(")"); + } + return target; +} + +// PID control +void Axis::configureStagePID(bool flip_direction, uint16_t transitions_per_rev) { + // enable the encoder at runtime (takes effect after the host sends it; getCurrentPositionMicrosteps will then read ENC_POS) + _config.enableEncoder = true; + + // ENC-2 tripwire: the runtime flip is the authoritative value and should match the config.h boot default invertEncoderDir. + // A mismatch means constants.py and config.h have decoupled encoder directions (only one side changed) -> warn. + // Then sync _config.invertEncoderDir to the actually-effective value so this field always reflects the true hardware direction. + if (flip_direction != _config.invertEncoderDir) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":WARN encoder flip mismatch boot="); + DEBUG_PRINT(_config.invertEncoderDir); + DEBUG_PRINT(" runtime="); + DEBUG_PRINTLN(flip_direction); + } + _config.invertEncoderDir = flip_direction; + + // ABN encoder initialization (hardcoded parameters match the old architecture) + motor_initABNEncoder(_icID, transitions_per_rev, + 32, // filter_wait_time + 4, // filter_exponent + 512, // filter_vmean + flip_direction); + + // PID parameter initialization (differentiated by axis type) + // pid_dclip = VMAX (internal units), already cached in motorParams + uint32_t vmax_usteps = (uint32_t)motorParams[_icID].vmax; + uint32_t target_tolerance, pid_tolerance, pid_iclip; + + // differentiate parameters by axis name + if (strcmp(_axisName, "W") == 0 || strcmp(_axisName, "W2") == 0) { + // 2026-05-26 speed optimization: target_tolerance / pid_tolerance 2->20 let the chip finish the end-of-move settling earlier, + // while suppressing PID hunting (position accuracy +/-1.8deg, visually imperceptible on the 45deg/slot filter wheel). + // 2026-05-27: tried tightening pid_tolerance=5 but never flashed/tested; the final hardware-verified config is ms=8 + P=8192 + tol=20. + target_tolerance = 20; + pid_tolerance = 20; + pid_iclip = 4096; + } else if (strcmp(_axisName, "Z") == 0) { + target_tolerance = 25; + pid_tolerance = 25; + pid_iclip = 4096; + } else { + // X, Y and others + target_tolerance = 25; + pid_tolerance = 25; + pid_iclip = 32767; + } + + motor_initPID(_icID, target_tolerance, pid_tolerance, + _pidState.p, _pidState.i, _pidState.d, + vmax_usteps, pid_iclip, 2); // pid_d_clkdiv = 2 + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":CONFIGURE_STAGE_PID flip="); + DEBUG_PRINT(flip_direction); + DEBUG_PRINT(" tpr="); + DEBUG_PRINT(transitions_per_rev); + DEBUG_PRINT(" P="); + DEBUG_PRINT(_pidState.p); + DEBUG_PRINT(" I="); + DEBUG_PRINT(_pidState.i); + DEBUG_PRINT(" D="); + DEBUG_PRINTLN(_pidState.d); +} + +void Axis::enableStagePID() { + _pidState.enabled = true; + motor_enablePID(_icID); + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":ENABLE_STAGE_PID"); +} + +void Axis::disableStagePID() { + _pidState.enabled = false; + motor_disablePID(_icID); + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":DISABLE_STAGE_PID"); +} + +void Axis::setPIDArguments(uint16_t p, uint8_t i, uint8_t d) { + _pidState.p = p; + _pidState.i = i; + _pidState.d = d; + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":SET_PID_ARGUMENTS P="); + DEBUG_PRINT(p); + DEBUG_PRINT(" I="); + DEBUG_PRINT(i); + DEBUG_PRINT(" D="); + DEBUG_PRINTLN(d); +} + +// Update the lead-screw pitch at runtime +void Axis::setLeadScrewPitch(float pitchMM) { + _config.screwPitchMM = pitchMM; + motorParams[_icID].screwPitchMM = pitchMM; + motorParams[_icID].stepsPerMM = + (float)(motorParams[_icID].fullStepsPerRev * motorParams[_icID].microsteps) / + pitchMM; +} + +// Reconfigure the stepper driver at runtime (microstepping + current) +void Axis::configureDriver(uint16_t microstepping, float currentMA, + float holdCurrentRatio) { + _config.microstepping = microstepping; + // Note: do not sync homingMicrostepping -- on Y, 256 microsteps + 30 mm/s measured as quietest, + // so even if legacy Squid software sends 32 microsteps for running, homing still switches to 256 microsteps. + _config.motorCurrentMA = currentMA; + _config.holdCurrent = holdCurrentRatio; + + // update the TMC4361A controller-side microstepping + stepsPerMM cache + motor_setMicrosteps(_icID, microstepping); + + // reinitialize the driver (current + chopper parameters) + MotorConfig motorConfig = { + .driverType = _config.driverType, + .rSense = _config.r_sense, + .runCurrentMA = currentMA, + .holdCurrentRatio = holdCurrentRatio, + .microstepRes = 0, + .interpolation = true, + .toff = 3, + .hstrt = 0, // match legacy Squid zero-hysteresis (see the begin() comment) + .hend = 0, + .tbl = 2, + .stallThreshold = (int8_t)_config.stallSensitivity, + .stallFilter = true, + .enableStealthChop = false, + .globalScaler = 0, + .iholdDelay = 7, + .currentRange = _config.currentRange}; + motor_initDriver(_icID, &motorConfig); + + // a microstepping change alters stepsPerMM, so recompute the motion parameters + setMotionParameters(_config.maxVelocityMM, _config.maxAccelerationMM); +} + +// Update the homing safety margin at runtime +void Axis::setHomeSafetyMargin(float marginMM) { + _config.homeSafetyMarginMM = marginMM; + motor_enableHomingLimit(_icID, _config.rightSwitchPolarity, + _config.homingSwitch, + mmToMicrosteps(marginMM)); +} + +// Re-write the _config limit configuration into the chip at runtime (polarity is now sent by the host via cmd 20 and must be reapplied to take effect) +void Axis::reapplyLimitSwitches() { + LimitConfig limitConfig = { + .enableLeft = _config.enableLeftLimitSwitch, + .enableRight = _config.enableRightLimitSwitch, + .leftPolarity = _config.leftSwitchPolarity, + .rightPolarity = _config.rightSwitchPolarity, + .leftFlipped = _config.leftFlipped, + .rightFlipped = _config.rightFlipped, + .homingSwitch = _config.homingSwitch, + .homeSafetyMarginMM = _config.homeSafetyMarginMM}; + motor_configLimitSwitches(_icID, &limitConfig); + motor_enableHomingLimit(_icID, _config.rightSwitchPolarity, + _config.homingSwitch, + mmToMicrosteps(_config.homeSafetyMarginMM)); +} + +// Get the current state +AxisState Axis::getCurrentState() const { return _currentState; } + +// Get the axis name +const char *Axis::getAxisName() const { return _axisName; } + +// Check whether in the error state +bool Axis::isInErrorState() const { return _currentState == STATE_ERROR; } + +// Read the electronic limit-switch state (using the new API) +uint8_t Axis::readLimitSwitches() const { + return motor_readLimitSwitches(_icID); +} + +// Read switch events (using the new API) +uint8_t Axis::readSwitchEvent() const { + return motor_readSwitchEvent(_icID); +} + +// Read axis events (using the new API) +uint32_t Axis::readAxisEvent() const { + return motor_readEvents(_icID); +} + +// Private method implementations +void Axis::setState(AxisState newState) { + if (_currentState != newState) { + _previousState = _currentState; + _currentState = newState; + _stateStartTime = millis(); + _stateChanged = true; // mark the state as changed + } +} + +void Axis::handleError(const char *errorMsg) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Axis Error: "); + DEBUG_PRINTLN(errorMsg); + smoothStop(); + setState(STATE_ERROR); +} + +bool Axis::checkTimeout(unsigned long timeoutMs) const { + return (millis() - _stateStartTime) > timeoutMs; +} + + +// Unit-conversion functions (using the new API) +int32_t Axis::mmToMicrosteps(float mm) const { + return motor_mmToMicrosteps(_icID, mm); +} + +float Axis::microstepsToMM(int32_t microsteps) const { + return motor_microstepsToMM(_icID, microsteps); +} + +uint32_t Axis::velocityMMToMicrosteps(float velocityMM) const { + return motor_velocityMMToInternal(_icID, velocityMM); +} + +uint32_t Axis::accelerationMMToMicrosteps(float accelerationMM) const { + return motor_accelMMToInternal(_icID, accelerationMM); +} + +bool Axis::isValidPosition(float positionMM) const { + // check whether the position is within a reasonable range + return (positionMM >= -1000.0f && positionMM <= 1000.0f); // adjust to actual conditions +} + +bool Axis::isWithinSoftLimits(int32_t microsteps) const { + // this should check against the actual soft-limit settings + // temporarily returns true; needs to be completed per the concrete implementation + return true; +} + +bool Axis::handleEmergency() { + // send the axis state + [[maybe_unused]] const char *stateStr = "UNKNOWN"; + switch (_currentState) { + case STATE_IDLE: + stateStr = "IDLE"; + break; + case STATE_HOMING_INIT: + stateStr = "HOMING_INIT"; + break; + case STATE_HOMING_SEARCH: + stateStr = "HOMING_SEARCH"; + break; + case STATE_HOMING_SET_ZERO: + stateStr = "HOMING_SET_ZERO"; + break; + case STATE_LEAVING_HOME: + stateStr = "LEAVING_HOME"; + break; + case STATE_MOVING: + stateStr = "MOVING"; + break; + case STATE_ERROR: + stateStr = "ERROR"; + break; + } + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":EMERGENCY:"); + DEBUG_PRINTLN(stateStr); + + return true; +} + +bool Axis::handleGetData() { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":GET_DATA:START"); // debug point 1 + + // send the axis state + [[maybe_unused]] const char *stateStr = "UNKNOWN"; + switch (_currentState) { + case STATE_IDLE: + stateStr = "IDLE"; + break; + case STATE_HOMING_INIT: + stateStr = "HOMING_INIT"; + break; + case STATE_HOMING_SEARCH: + stateStr = "HOMING_SEARCH"; + break; + case STATE_HOMING_SET_ZERO: + stateStr = "HOMING_SET_ZERO"; + break; + case STATE_LEAVING_HOME: + stateStr = "LEAVING_HOME"; + break; + case STATE_MOVING: + stateStr = "MOVING"; + break; + case STATE_ERROR: + stateStr = "ERROR"; + break; + } + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":STATE:"); + DEBUG_PRINTLN(stateStr); + + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":GET_DATA:BEFORE_GET_POS"); // debug point 2 + + // send the current position + int32_t microsteps = getCurrentPosition(); + + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":GET_DATA:AFTER_GET_POS"); // debug point 3 + [[maybe_unused]] float positionMM = microstepsToMM(microsteps); + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Current Position (mm):"); + DEBUG_PRINTLNF(positionMM, 3); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Current Position (microsteps):"); + DEBUG_PRINTLN(microsteps); + + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":GET_DATA:BEFORE_READ_LIMIT"); // debug point 4 + + // send the limit-switch state + [[maybe_unused]] uint8_t limitState = readLimitSwitches(); + + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":GET_DATA:AFTER_READ_LIMIT"); // debug point 5 + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":LIMIT_SWITCHES:0x"); + DEBUG_PRINTLNF(limitState, HEX); + + // Added: send the movement state + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":IS_MOVING:"); + DEBUG_PRINTLN(_isMoving ? "YES" : "NO"); + + // Added: send the enable state + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":IS_ENABLED:"); + DEBUG_PRINTLN(_isEnabled ? "YES" : "NO"); + + // send aggregate status info (for label display) + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":AXIS_STATUS:"); + DEBUG_PRINT(stateStr); + DEBUG_PRINT(" | Pos:"); + DEBUG_PRINTF(positionMM, 3); + DEBUG_PRINT("mm | Moving:"); + DEBUG_PRINT(_isMoving ? "YES" : "NO"); + DEBUG_PRINT(" | Enabled:"); + DEBUG_PRINT(_isEnabled ? "YES" : "NO"); + DEBUG_PRINT(" | Limits:0x"); + DEBUG_PRINTLNF(limitState, HEX); + + return true; +} + +bool Axis::handleAxisAbilityToggle(bool action) { + if (action == true) { + enableAxis(); + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":AXIS Enable"); + } else { + disableAxis(); + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":AXIS Disable"); + } + return true; +} + +bool Axis::handleDebugReg() { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":DEBUG_REG:START"); + + // read the key TMC4361A registers + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:GENERAL_CONF(0x00)=0x"); + DEBUG_PRINTLNF(tmc4361A_readRegister(_icID, TMC4361A_GENERAL_CONF), HEX); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:REFERENCE_CONF(0x01)=0x"); + DEBUG_PRINTLNF(tmc4361A_readRegister(_icID, TMC4361A_REFERENCE_CONF), HEX); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:SPI_OUT_CONF(0x05)=0x"); + DEBUG_PRINTLNF(tmc4361A_readRegister(_icID, TMC4361A_SPI_OUT_CONF), HEX); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:STATUS(0x0E)=0x"); + DEBUG_PRINTLNF(tmc4361A_readRegister(_icID, TMC4361A_STATUS), HEX); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:EVENTS(0x0F)=0x"); + DEBUG_PRINTLNF(tmc4361A_readRegister(_icID, TMC4361A_EVENTS), HEX); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:CLK_FREQ(0x1F)="); + DEBUG_PRINTLN(tmc4361A_readRegister(_icID, TMC4361A_CLK_FREQ)); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:RAMPMODE(0x20)=0x"); + DEBUG_PRINTLNF(tmc4361A_readRegister(_icID, TMC4361A_RAMPMODE), HEX); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:XACTUAL(0x21)="); + DEBUG_PRINTLN(tmc4361A_readRegister(_icID, TMC4361A_XACTUAL)); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:VACTUAL(0x22)="); + DEBUG_PRINTLN(tmc4361A_readRegister(_icID, TMC4361A_VACTUAL)); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:XTARGET(0x2D)="); + DEBUG_PRINTLN(tmc4361A_readRegister(_icID, TMC4361A_XTARGET)); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:VMAX(0x24)="); + DEBUG_PRINTLN(tmc4361A_readRegister(_icID, TMC4361A_VMAX)); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:AMAX(0x28)="); + DEBUG_PRINTLN(tmc4361A_readRegister(_icID, TMC4361A_AMAX)); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:DMAX(0x29)="); + DEBUG_PRINTLN(tmc4361A_readRegister(_icID, TMC4361A_DMAX)); + + // Added: key configuration registers + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:STEP_CONF(0x0A)=0x"); + DEBUG_PRINTLNF(tmc4361A_readRegister(_icID, TMC4361A_STEP_CONF), HEX); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:CURRENT_CONF(0x05)=0x"); + DEBUG_PRINTLNF(tmc4361A_readRegister(_icID, TMC4361A_CURRENT_CONF), HEX); + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":REG:SCALE_VALUES(0x06)=0x"); + DEBUG_PRINTLNF(tmc4361A_readRegister(_icID, TMC4361A_SCALE_VALUES), HEX); + + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":DEBUG_REG:END"); + + return true; +} diff --git a/firmware/octoaxes/axis.h b/firmware/octoaxes/axis.h new file mode 100644 index 000000000..54a262db4 --- /dev/null +++ b/firmware/octoaxes/axis.h @@ -0,0 +1,309 @@ +#ifndef AXIS_H +#define AXIS_H + +#include "tmc/motion/MotorControl.h" +#include + +// Limit switch and direction constants +#define LEFT_SW 0b01 +#define RGHT_SW 0b10 +#define LEFT_DIR -1 +#define RGHT_DIR 1 +#define OBSW_SW 0b01 // used by the Objectives class + +// Driver chip type: DRIVER_TMC2660 / DRIVER_TMC2240 (defined in MotorControl.h) + +// State definitions - using more explicit state names +enum AxisState { + STATE_IDLE, + STATE_HOMING_INIT, + STATE_HOMING_SEARCH, + STATE_HOMING_SET_ZERO, + STATE_LEAVING_HOME, + STATE_MOVING, + STATE_ERROR +}; + +class Axis { + +public: + // Configuration parameters + struct AxisConfig { + uint32_t clockFrequency; + uint8_t homingSwitch; + uint8_t leftSwitchPolarity; + uint8_t rightSwitchPolarity; + // Whether the host cmd 20 (SET_LIM_SWITCH_POLARITY) is allowed to write the polarity into the chip REFERENCE_CONF. + // Only Z=true (polarity changes with the old/new Z variant and must be sent by software at runtime); axes with + // fixed hardware polarity such as X/Y=false: cmd 20 only updates the struct and does not touch the chip, matching + // the legacy Squid firmware behavior (legacy Squid cmd 20 also only sets a software variable and never writes the + // chip), which avoids the X/Y polarity (active-high) sent by legacy Squid wrongly flipping the octoaxes hardware (active-low). + bool polarityAffectsChip = false; // default false (axes that omit this field = do not write the chip); only Z_AXIS sets it true explicitly + uint8_t leftIsInactive; + uint8_t rightIsInactive; + bool leftFlipped; + bool rightFlipped; + bool enableLeftLimitSwitch; + bool enableRightLimitSwitch; + float r_sense; + float screwPitchMM; + int fullStepsPerRev; + int microstepping; + int homingMicrostepping; // microstepping used during homing, default 256 + float maxVelocityMM; + float maxAccelerationMM; + float homingVelocityMM; + float motorCurrentMA; // peak current (mA), I_rms = I_peak / √2 + float holdCurrent; + float homeSafetyMarginMM; + float homeSafetyPositionMM; + bool enableStallSensitivity; + int stallSensitivity; + bool useSShapedRamp; // true=S-shaped ramp, false=trapezoidal ramp + float astartMM; // start acceleration (mm/s²), 0=unused + float dfinalMM; // final deceleration (mm/s²), 0=same as astart + uint32_t homing_timeout_ms; + int8_t homing_direct; + uint8_t driverType; // driver chip model, default DRIVER_TMC2660 + uint8_t currentRange; // TMC2240 CURRENT_RANGE: 0=1A, 1=2A, 2=3A (ignored for TMC2660) + bool enableEncoder; // whether to enable the ABN encoder, default false + uint16_t encoderLinesPerRev; // encoder lines (per revolution), e.g. 4000. Used directly as transitions + bool invertEncoderDir; // reverse the encoder counting direction, default false + bool invert_direction; // 2026-05-25 hardware direction inversion: when true, all MOVE/HOMING commands + // invert their payload at the firmware level, so mirror-assembled hardware (whose + // home flag bit is opposite to the legacy Squid design) reaches the correct physical + // position using the same host commands. + // moveTo/moveRelative invert target/delta; + // getCurrentPositionMicrosteps inverts the chip XACTUAL; + // filterwheel.cpp homing search inverts the velocity direction. + // default false (fully consistent with legacy Squid behavior). + }; + +protected: + // Protected member variables, accessible to derived classes + uint8_t _csPin; + uint8_t _axisIndex; + const char *_axisName; + + // IC identifier + uint8_t _icID; + + // Motion parameters + uint32_t _maxVelocityMicrosteps; + uint32_t _maxAccelerationMicrosteps; + + // Added: state-change detection + AxisState _lastReportedState; // last reported state + bool _stateChanged; // flag for whether the state changed + unsigned long _lastStateReportTime; // time of the last state report + + // State variables + AxisState _currentState; + AxisState _previousState; + unsigned long _stateStartTime; + bool _homeFound; + + // Added: movement state flags + bool _isMoving; + int32_t _moveDirection; + unsigned long _cmdRecvMicros; // command-received time (micros) + unsigned long _moveStartMicros; // movement-start time (micros) + + // Added: axis enable state + bool _isEnabled; + + // Soft-limit state tracking (for automatic restore after homing) + bool _softLimitsEnabled; + + // Shadow state for the direction-aware soft-limit gate: + // After a one-sided SET_LIM, record the host's intent, decoupled from the chip registers. + // Even if motor_moveToMicrosteps recovery temporarily clears the chip's EN bit, + // this still preserves the "was this side ever set" semantics, used by isMoveAllowedByDirection(). + struct SoftLimitShadow { + bool leftEnabled; // whether X-/Y-/Z- was set by SET_LIM + bool rightEnabled; // whether X+/Y+/Z+ was set by SET_LIM + int32_t leftValue; // latest set value of VIRT_STOP_LEFT (microsteps) + int32_t rightValue; // latest set value of VIRT_STOP_RIGHT (microsteps) + }; + SoftLimitShadow _softLimits = {false, false, INT32_MIN, INT32_MAX}; + + // Flag for delayed re-enable after virtual-limit recovery + // motor_moveToMicrosteps() disables limits during VSTOP recovery, + // so they can only be re-enabled after the motor leaves the boundary (VSTOP flags cleared in STATUS) + bool _needReenableLimits; + + // PID state (independent per axis) + struct PIDState { + bool enabled; // whether PID is currently active + uint16_t p; // cached P parameter + uint8_t i; // cached I parameter + uint8_t d; // cached D parameter + } _pidState = {false, 0, 0, 0}; + + AxisConfig _config; + + // Timeout settings + static const unsigned long LEAVING_HOME_TIMEOUT_MS = 5000; + static const unsigned long MOVEMENT_TIMEOUT_MS = 5000; + + elapsedMicros _checkHomeReachTimeout; + + // Throttle for STATE_MOVING checkLimitPosition (matches legacy Squid check_limits 10ms throttle, + // reduces SPI bus contention; the hard-limit completion check tolerates a 0-10ms delay since the + // chip has already physically stopped internally) + // (#5, 2026-05-19) + elapsedMicros _limitCheckThrottle; + + uint32_t _homing_timeout_ms; + +public: + // Constructor + Axis(uint8_t csPin, uint8_t axisIndex, const char *axisName); + + // Virtual destructor + virtual ~Axis() = default; + + // Initialization function + virtual bool begin(const AxisConfig &config); + + // State-machine update - declared virtual so derived classes can override + virtual void update(); + + // Limit-position check + virtual void checkLimitPosition(); + + // Added: movement-complete detection called inside the ISR + virtual void checkMovementComplete(); + + // Command processing - returns the result + virtual bool processCommand(const String &command); + + // Added: state-report control + virtual void reportStateIfChanged(bool force = false); + virtual void setStateChangeFlag() { _stateChanged = true; } + + // Motion control + virtual bool moveToPosition(float positionMM); + virtual bool moveRelative(float distanceMM); + virtual bool moveToPositionMicrosteps(int32_t targetMicrosteps); + virtual bool moveRelativeMicrosteps(int32_t deltaMicrosteps); + virtual void setSpeed(float speedMM); + virtual void smoothStop(); + + void disableAxis(); + void enableAxis(); + + // Position control + virtual void setCurrentPosition(float positionMM); + virtual float getCurrentPositionMM() const; + virtual int32_t getCurrentPosition() const; + virtual int32_t getCurrentPositionMicrosteps() const; + virtual int32_t getEncoderPositionMicrosteps() const; + virtual void setMotionParameters(float maxVelocityMM, + float maxAccelerationMM); + + // Homing control + virtual bool startHoming(); + virtual bool handleReset(); + virtual bool handleDebugReg(); + virtual bool isHomingInProgress() const; + virtual bool isMovementComplete() const; + + // Added: movement-state query + virtual bool isMoving() const { return _isMoving; } + + // Added: enable-state query + virtual bool isEnabled() const { return _isEnabled; } + + // Soft-limit state query + bool isSoftLimitsEnabled() const { return _softLimitsEnabled; } + + // Limit configuration + virtual void setSoftLimits(float lowerLimitMM, float upperLimitMM); + virtual void enableSoftLimits(bool enable); + void setOneSoftLimit(int direction, int32_t valueMicrosteps); + + // Direction-aware clamp: clamp the target to the range allowed by the "move toward the safer direction" principle + // With current position C, target T, and _softLimits leftValue=L / rightValue=R: + // effective_lower = (C ≤ L) ? C : L // when past the lower limit, forbid going further down; in the safe zone, lower bound = L + // effective_upper = (C ≥ R) ? C : R // symmetric + // returns clamp(T, effective_lower, effective_upper) + // The side that is not enabled does not participate in clamping. After clamping to the boundary the motor stops at + // the boundary, compatible with legacy Squid (legacy Squid also does min/max clamp in firmware callback_move_x/y/z) + int32_t clampTargetByDirection(int32_t targetMicrosteps) const; + + // PID control + void configureStagePID(bool flip_direction, uint16_t transitions_per_rev); + void enableStagePID(); + void disableStagePID(); + void setPIDArguments(uint16_t p, uint8_t i, uint8_t d); + bool isPIDEnabled() const { return _pidState.enabled; } + + // Runtime configuration updates + void setLeadScrewPitch(float pitchMM); + void configureDriver(uint16_t microstepping, float currentMA, + float holdCurrentRatio); + void setHomeSafetyMargin(float marginMM); + // Re-write the limit polarity/flip/enable/homingSwitch from _config into the chip REFERENCE_CONF at runtime. + // begin() only configures once at boot; after cmd 20 (SET_LIM_SWITCH_POLARITY) updates the struct, this method must be called for it to actually take effect. + void reapplyLimitSwitches(); + + // Configuration access + uint8_t getIcID() const { return _icID; } + const AxisConfig &getConfig() const { return _config; } + AxisConfig &getMutableConfig() { return _config; } + + // State query + virtual AxisState getCurrentState() const; + virtual const char *getAxisName() const; + uint8_t getDriverType() const { return _config.driverType; } + virtual bool isInErrorState() const; + virtual uint32_t readAxisEvent() const; + + // Limit-switch state + virtual uint8_t readLimitSwitches() const; + virtual uint8_t readSwitchEvent() const; + + // Axis-move interface + bool moveAxis(int32_t value); + +protected: + // Protected member methods, accessible to derived classes + virtual void performHomingSequence() = 0; + virtual void performLeavingHome() = 0; + + // Homing microstepping switch + void switchToHomingMicrosteps(); + void restoreNormalMicrosteps(); + + virtual void setState(AxisState newState); + virtual void handleError(const char *errorMsg); + virtual bool checkTimeout(unsigned long timeoutMs) const; + virtual int32_t hexStringToInt32(String hex); + + // Command-handling helper methods + virtual bool handleGetPosition(); + virtual bool handleSetLimits(const String &command) = 0; + virtual bool handleMoveAxis(const String &command); + virtual bool handleMoveToAxis(const String &command); + virtual bool handleHoming(); + virtual bool handleGetData(); + virtual bool handleEmergency(); + virtual bool handleAxisAbilityToggle(bool); + + // Unit conversion + virtual int32_t mmToMicrosteps(float mm) const; + virtual float microstepsToMM(int32_t microsteps) const; + virtual uint32_t velocityMMToMicrosteps(float velocityMM) const; + virtual uint32_t accelerationMMToMicrosteps(float accelerationMM) const; + + // Motion checks + virtual bool isValidPosition(float positionMM) const; + virtual bool isWithinSoftLimits(int32_t microsteps) const; + + // Added: movement-state management + virtual void startMovement(); + virtual void completeMovement(); +}; + +#endif diff --git a/firmware/octoaxes/build_opt.h b/firmware/octoaxes/build_opt.h new file mode 100644 index 000000000..fa4446197 --- /dev/null +++ b/firmware/octoaxes/build_opt.h @@ -0,0 +1,30 @@ +#ifndef INCLUDED_BUILD_OPT_H +#define INCLUDED_BUILD_OPT_H + +#define ENABLE_LED_INDICATOR + +// ENABLE_DEBUG is only enabled in debug builds (the platformio.ini debug env passes -D DEBUG) +// Production builds (teensy41) do not define DEBUG; the serial port outputs only binary protocol packets, no ASCII text +#ifdef DEBUG + #define ENABLE_DEBUG +#endif + +#ifdef ENABLE_DEBUG + #define DEBUG_PRINT(x) SerialUSB.print(x) + #define DEBUG_PRINTLN(x) SerialUSB.println(x) + #define DEBUG_PRINTF(x, y) SerialUSB.print(x, y) + #define DEBUG_PRINTLNF(x, y) SerialUSB.println(x, y) +#else + #define DEBUG_PRINT(x) + #define DEBUG_PRINTLN(x) + #define DEBUG_PRINTF(x, y) + #define DEBUG_PRINTLNF(x, y) +#endif + +// Uncomment the line below -> temporarily disable the 24-byte binary position reporting so SerialUSB outputs only ASCII, +// making it easy to view DEBUG_PRINT output in the Arduino IDE Serial Monitor / a plain terminal +// without binary garbage interfering. Note: once disabled the host cannot receive position reports and cannot connect. +// === Enable when debugging [FOCUS] / other ASCII output; comment back out when done === +// #define DISABLE_BINARY_POS_UPDATE + +#endif /* INCLUDED_BUILD_OPT_H */ diff --git a/firmware/octoaxes/commandprocessor.cpp b/firmware/octoaxes/commandprocessor.cpp new file mode 100644 index 000000000..3973fb673 --- /dev/null +++ b/firmware/octoaxes/commandprocessor.cpp @@ -0,0 +1,587 @@ +#include "commandprocessor.h" +#include "axesmrg.h" +#include "build_opt.h" +#include "illumination.h" +#include "trigger.h" +#include "config.h" + +// Protocol constants (from Squid constants_protocol.h) +static const int HOME_POSITIVE = 0; +static const int HOME_NEGATIVE = 1; +static const int HOME_OR_ZERO_ZERO = 2; + +// SET_LIM limit codes +static const int LIM_CODE_X_POSITIVE = 0; +static const int LIM_CODE_X_NEGATIVE = 1; +static const int LIM_CODE_Y_POSITIVE = 2; +static const int LIM_CODE_Y_NEGATIVE = 3; +static const int LIM_CODE_Z_POSITIVE = 4; +static const int LIM_CODE_Z_NEGATIVE = 5; + +// Limit-switch polarity +static const int POLARITY_ACTIVE_LOW = 0; +static const int POLARITY_ACTIVE_HIGH = 1; +static const int POLARITY_DISABLED = 2; + +// offset velocity (consistent with the old architecture globals.cpp) +// enable_offset_velocity is already defined in def_octopi_80120.h +float offset_velocity_x = 0; +float offset_velocity_y = 0; + +// protocol axis value -> axis name (nullptr = invalid axis) +static const char* protocolAxisToName(uint8_t protocolAxis) { + switch (protocolAxis) { + case 0: return "X"; + case 1: return "Y"; + case 2: return "Z"; + case 5: return "W"; + case 6: return "W2"; + case 7: return "Turret"; // 2026-05-29 objective turret (HOME_OR_ZERO axis=7) + default: return nullptr; + } +} + +CommandProcessor commandProcessor; + +CommandProcessor::CommandProcessor() { + // constructor initialization code +} + +CommandProcessor::~CommandProcessor() { + // destructor cleanup code +} + +// the following are the implementation skeletons of the individual command handlers +void CommandProcessor::handleMoveX(const byte *data) { + int32_t relative_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("X"); + if (axis) + axis->moveAxis(relative_position); + + DEBUG_PRINTLN("Get MoveX Command"); +} + +void CommandProcessor::handleMoveY(const byte *data) { + int32_t relative_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("Y"); + if (axis) + axis->moveAxis(relative_position); + + DEBUG_PRINTLN("Get MoveY Command"); +} + +void CommandProcessor::handleMoveZ(const byte *data) { + int32_t relative_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("Z"); + if (axis) + axis->moveAxis(relative_position); + + DEBUG_PRINTLN("Get MoveZ Command"); +} + +void CommandProcessor::handleMoveTheta(const byte *data) { + // TODO: implement MOVE_THETA command handling + DEBUG_PRINTLN("CMD_NOT_IMPLEMENTED: MOVE_THETA"); +} + +void CommandProcessor::handleMoveW(const byte *data) { + int32_t relative_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("W"); + if (axis) + axis->moveAxis(relative_position); + + DEBUG_PRINTLN("Get MoveW Command"); +} + +void CommandProcessor::handleHomeOrZero(const byte *data) { + // data[2]: protocol axis value (0=X,1=Y,2=Z,4=XY,5=W,6=W2) + // data[3]: HOME_POSITIVE=0 (toward +), HOME_NEGATIVE=1 (toward -), HOME_OR_ZERO_ZERO=2 (zero only) + if (data[3] == HOME_OR_ZERO_ZERO) { + // zero mode: set the current position to 0, no movement + if (data[2] == 4) { // AXES_XY combined + Axis *axX = axisManager.findAxisByName("X"); + Axis *axY = axisManager.findAxisByName("Y"); + if (axX) axX->setCurrentPosition(0.0f); + if (axY) axY->setCurrentPosition(0.0f); + } else { + const char *name = protocolAxisToName(data[2]); + if (name) { + Axis *axis = axisManager.findAxisByName(name); + if (axis) axis->setCurrentPosition(0.0f); + } + } + return; + } + // Homing mode (HOME_POSITIVE / HOME_NEGATIVE): + // 2026-05-11: parse the direction from protocol data[3], compatible with legacy Squid software + // legacy Squid microcontroller.py:88 derives data[3] from stage_movement_sign_x, + // legacy Squid firmware (main_controller_teensy41.ino:1252) reads data[3] to decide the direction. + // previously octoaxes ignored data[3] and used only config.homing_direct, which behaves correctly under the octoaxes GUI + // (constants.py's sign pairs consistently with config.homing_direct), but when legacy Squid software's + // data[3] is not interpreted -> the direction may reverse (X homes toward the physical + end and hits the limit). + // + // compatibility strategy: override config.homing_direct with data[3]: + // HOME_POSITIVE (0) -> homing_direct = +1 (toward +) + // HOME_NEGATIVE (1) -> homing_direct = -1 (toward -) + // written permanently into _config; subsequent startHoming() then uses the new direction. + int8_t new_direct = (data[3] == HOME_NEGATIVE) ? -1 : +1; + if (data[2] == 4) { // AXES_XY combined homing + Axis *axX = axisManager.findAxisByName("X"); + Axis *axY = axisManager.findAxisByName("Y"); + if (axX) { + axX->getMutableConfig().homing_direct = new_direct; + axX->startHoming(); + } + if (axY) { + axY->getMutableConfig().homing_direct = new_direct; + axY->startHoming(); + } + } else { + const char *name = protocolAxisToName(data[2]); + if (name) { + Axis *axis = axisManager.findAxisByName(name); + if (axis) { + axis->getMutableConfig().homing_direct = new_direct; + axis->startHoming(); + } + } + } +} + +void CommandProcessor::handleMoveToX(const byte *data) { + int32_t absolute_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("X"); + if (axis) + axis->moveToPositionMicrosteps(absolute_position); + + DEBUG_PRINTLN("Get MoveToX Command"); +} + +void CommandProcessor::handleMoveToY(const byte *data) { + int32_t absolute_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("Y"); + if (axis) + axis->moveToPositionMicrosteps(absolute_position); + + DEBUG_PRINTLN("Get MoveToY Command"); +} + +void CommandProcessor::handleMoveToZ(const byte *data) { + int32_t absolute_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("Z"); + if (axis) + axis->moveToPositionMicrosteps(absolute_position); + + DEBUG_PRINTLN("Get MoveToZ Command"); +} + +void CommandProcessor::handleSetLim(const byte *data) { + // data[2]: LIM_CODE (0-5), data[3..6]: limit value (microsteps, 32-bit big-endian) + int32_t value = int32_t((uint32_t(data[3]) << 24) | (uint32_t(data[4]) << 16) | + (uint32_t(data[5]) << 8) | uint32_t(data[6])); + const char *axisName = nullptr; + int direction = 0; + switch (data[2]) { + case LIM_CODE_X_POSITIVE: axisName = "X"; direction = 1; break; + case LIM_CODE_X_NEGATIVE: axisName = "X"; direction = -1; break; + case LIM_CODE_Y_POSITIVE: axisName = "Y"; direction = 1; break; + case LIM_CODE_Y_NEGATIVE: axisName = "Y"; direction = -1; break; + case LIM_CODE_Z_POSITIVE: axisName = "Z"; direction = 1; break; + case LIM_CODE_Z_NEGATIVE: axisName = "Z"; direction = -1; break; + default: return; + } + Axis *axis = axisManager.findAxisByName(axisName); + if (axis) + axis->setOneSoftLimit(direction, value); +} + +void CommandProcessor::handleTurnOnIllumination(const byte *data) { + // matches legacy Squid main_controller_teensy41.ino:1529: do not touch illumination_source. + // the legacy Squid host's turn_on_illumination() command packet has cmd[2]=0; if we read data[2] and write source here, + // it would force source to 0 (LED_ARRAY_FULL = brightfield), lighting up brightfield after switching to a fluorescence channel. + turn_on_illumination(); +} + +void CommandProcessor::handleTurnOffIllumination(const byte *data) { + turn_off_illumination(); +} + +void CommandProcessor::handleSetIllumination(const byte *data) { + set_illumination(data[2], (uint16_t(data[3]) << 8) + uint16_t(data[4])); +} + +void CommandProcessor::handleSetIlluminationLEDMatrix(const byte *data) { + set_illumination_led_matrix(data[2], data[3], data[4], data[5]); +} + +void CommandProcessor::handleAckJoystickButtonPressed(const byte *data) { + joystick_button_pressed = false; +} + +void CommandProcessor::handleAnalogWriteOnboardDAC(const byte *data) { + int channel = data[2]; + uint16_t value = (uint16_t(data[3]) << 8) | uint16_t(data[4]); + set_DAC8050x_output(channel, value); +} + +void CommandProcessor::handleSetDAC80508RefDivGain(const byte *data) { + set_DAC8050x_gain(data[2], data[3]); +} + +void CommandProcessor::handleSetIlluminationIntensityFactor(const byte *data) { + illumination_intensity_factor = float(data[2]) / 100.0f; +} + +void CommandProcessor::handleSetPortIntensity(const byte *data) { + set_port_intensity(data[2], (uint16_t(data[3]) << 8) | uint16_t(data[4])); +} + +void CommandProcessor::handleTurnOnPort(const byte *data) { + turn_on_port(data[2]); +} + +void CommandProcessor::handleTurnOffPort(const byte *data) { + turn_off_port(data[2]); +} + +void CommandProcessor::handleSetPortIllumination(const byte *data) { + set_port_intensity(data[2], (uint16_t(data[3]) << 8) | uint16_t(data[4])); + if (data[5] != 0) turn_on_port(data[2]); + else turn_off_port(data[2]); +} + +void CommandProcessor::handleSetMultiPortMask(const byte *data) { + uint16_t port_mask = (uint16_t(data[2]) << 8) | uint16_t(data[3]); + uint16_t on_mask = (uint16_t(data[4]) << 8) | uint16_t(data[5]); + for (int i = 0; i < IlluminationConfig::NUM_PORTS; i++) { + if (port_mask & (1 << i)) { + if (on_mask & (1 << i)) turn_on_port(i); + else turn_off_port(i); + } + } +} + +void CommandProcessor::handleTurnOffAllPorts(const byte *data) { + turn_off_all_ports(); +} + +void CommandProcessor::handleSetWatchdogTimeout(const byte *data) { + uint32_t timeout = ((uint32_t)data[2] << 24) | ((uint32_t)data[3] << 16) + | ((uint32_t)data[4] << 8) | (uint32_t)data[5]; + watchdog_set_timeout(timeout); +} + +void CommandProcessor::handleHeartbeat(const byte *data) { + // no-op: the watchdog timer is already reset when a valid serial message is received +} + +void CommandProcessor::handleMoveW2(const byte *data) { + // legacy Squid MOVE_W2 (cmd 19): relative move, data[2..5] is int32 microsteps big-endian. + // when the W2 board is absent, axesmrg::beginAll has deleted this axis -> findAxisByName returns nullptr -> + // silent no-op (the response packet reports COMPLETED immediately). + int32_t relative_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("W2"); + if (axis) + axis->moveAxis(relative_position); + + DEBUG_PRINTLN("Get MoveW2 Command"); +} + +void CommandProcessor::handleMoveTurret(const byte *data) { + // 2026-05-29 MOVE_TURRET (cmd 44): objective turret relative move, data[2..5] is int32 microsteps big-endian. + // when the E1 board is absent, axesmrg::beginAll has deleted this axis -> findAxisByName returns nullptr -> silent no-op. + int32_t relative_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("Turret"); + if (axis) + axis->moveAxis(relative_position); + + DEBUG_PRINTLN("Get MoveTurret Command"); +} + +void CommandProcessor::handleMoveToTurret(const byte *data) { + // 2026-05-29 MOVETO_TURRET (cmd 45): objective turret absolute move. + int32_t absolute_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("Turret"); + if (axis) + axis->moveToPositionMicrosteps(absolute_position); + + DEBUG_PRINTLN("Get MoveToTurret Command"); +} + +void CommandProcessor::handleSetTriggerMode(const byte *data) { + if (data[2] <= 1) + trigger_mode = data[2]; +} + +void CommandProcessor::handleMoveToW(const byte *data) { + int32_t absolute_position = + int32_t((uint32_t(data[2]) << 24) + (uint32_t(data[3]) << 16) + + (uint32_t(data[4]) << 8) + uint32_t(data[5])); + Axis *axis = axisManager.findAxisByName("W"); + if (axis) + axis->moveToPositionMicrosteps(absolute_position); + + DEBUG_PRINTLN("Get MoveToW Command"); +} + +void CommandProcessor::handleSetLimSwitchPolarity(const byte *data) { + // data[2]: protocol axis; data[3]: polarity (0=ACTIVE_LOW, 1=ACTIVE_HIGH, 2=DISABLED) + if (data[3] == POLARITY_DISABLED) + return; + const char *name = protocolAxisToName(data[2]); + if (!name) return; + Axis *axis = axisManager.findAxisByName(name); + if (!axis) return; + uint8_t polarity = data[3]; + axis->getMutableConfig().leftSwitchPolarity = polarity; + axis->getMutableConfig().rightSwitchPolarity = polarity; + // only for axes with polarityAffectsChip=true (=Z), rewrite the polarity into the chip REFERENCE_CONF -- this is the key to the "Z-variant software switch" + // (the host sends the polarity per Z_AXIS_VARIANT, so switching needs no reflash). Fixed-hardware-polarity axes like X/Y only update the struct and do not touch the chip, + // consistent with legacy Squid firmware (legacy Squid cmd 20 also only sets a software variable), avoiding the X/Y polarity sent by legacy Squid wrongly flipping the chip. + if (axis->getConfig().polarityAffectsChip) + axis->reapplyLimitSwitches(); +} + +void CommandProcessor::handleConfigureStepperDriver(const byte *data) { + // data[2]: protocol axis; data[3]: microstepping; data[4..5]: RMS current (mA); data[6]: hold current (0-255) + const char *name = protocolAxisToName(data[2]); + if (!name) return; + Axis *axis = axisManager.findAxisByName(name); + if (!axis) return; + + // microstepping special handling: 0->1, 1-128->as-is, >128->256 + int microstepping = data[3]; + if (microstepping > 128) + microstepping = 256; + if (microstepping == 0) + microstepping = 1; + + float currentMA = float((uint16_t(data[4]) << 8) | uint16_t(data[5])); + float holdRatio = float(data[6]) / 255.0f; + + axis->configureDriver((uint16_t)microstepping, currentMA, holdRatio); +} + +void CommandProcessor::handleSetMaxVelocityAcceleration(const byte *data) { + // data[2]: protocol axis; data[3:4]: velocity*100 (mm/s); data[5:6]: acceleration*10 (mm/s2) + const char *name = protocolAxisToName(data[2]); + if (!name) return; + Axis *axis = axisManager.findAxisByName(name); + if (!axis) return; + float vel_mm = float((uint16_t(data[3]) << 8) | data[4]) / 100.0f; + float acc_mm = float((uint16_t(data[5]) << 8) | data[6]) / 10.0f; + axis->setMotionParameters(vel_mm, acc_mm); +} + +void CommandProcessor::handleSetLeadScrewPitch(const byte *data) { + // data[2]: protocol axis; data[3..4]: pitch*1000 (uint16, mm) + const char *name = protocolAxisToName(data[2]); + if (!name) return; + Axis *axis = axisManager.findAxisByName(name); + if (!axis) return; + + float pitchMM = float((uint16_t(data[3]) << 8) | uint16_t(data[4])) / 1000.0f; + axis->setLeadScrewPitch(pitchMM); +} + +void CommandProcessor::handleSetOffsetVelocity(const byte *data) { + // consistent with the old architecture callback_set_offset_velocity: + // only store the value when enable_offset_velocity is true, for use by the joystick loop + if (!enable_offset_velocity) return; + + // data[3..6]: int32 big-endian (um/s), /1000000 -> mm/s + float velocityMM = + float(int32_t(uint32_t(data[3]) << 24 | uint32_t(data[4]) << 16 | + uint32_t(data[5]) << 8 | uint32_t(data[6]))) / + 1000000.0f; + + switch (data[2]) { + case 0: offset_velocity_x = velocityMM; break; // AXIS_X + case 1: offset_velocity_y = velocityMM; break; // AXIS_Y + } +} + +void CommandProcessor::handleConfigureStagePID(const byte *data) { + // data[2]: protocol axis; data[3]: flip_direction; data[4:5]: transitions_per_rev (big-endian) + const char *name = protocolAxisToName(data[2]); + if (!name) return; + Axis *axis = axisManager.findAxisByName(name); + if (!axis) return; + bool flip_direction = data[3]; + uint16_t transitions_per_rev = (uint16_t(data[4]) << 8) | uint16_t(data[5]); + axis->configureStagePID(flip_direction, transitions_per_rev); +} + +void CommandProcessor::handleEnableStagePID(const byte *data) { + // data[2]: protocol axis + const char *name = protocolAxisToName(data[2]); + if (!name) return; + Axis *axis = axisManager.findAxisByName(name); + if (axis) axis->enableStagePID(); +} + +void CommandProcessor::handleDisableStagePID(const byte *data) { + // data[2]: protocol axis + const char *name = protocolAxisToName(data[2]); + if (!name) return; + Axis *axis = axisManager.findAxisByName(name); + if (axis) axis->disableStagePID(); +} + +void CommandProcessor::handleSetHomeSafetyMargin(const byte *data) { + // data[2]: protocol axis; data[3..4]: margin*1000 (uint16, mm) + const char *name = protocolAxisToName(data[2]); + if (!name) return; + Axis *axis = axisManager.findAxisByName(name); + if (!axis) return; + + float marginMM = float((uint16_t(data[3]) << 8) | uint16_t(data[4])) / 1000.0f; + axis->setHomeSafetyMargin(marginMM); +} + +void CommandProcessor::handleSetPIDArguments(const byte *data) { + // data[2]: protocol axis; data[3:4]: P (big-endian uint16); data[5]: I (uint8); data[6]: D (uint8) + const char *name = protocolAxisToName(data[2]); + if (!name) return; + Axis *axis = axisManager.findAxisByName(name); + if (!axis) return; + uint16_t p = (uint16_t(data[3]) << 8) | uint16_t(data[4]); + uint8_t i = data[5]; + uint8_t d = data[6]; + axis->setPIDArguments(p, i, d); +} + +void CommandProcessor::handleSendHardwareTrigger(const byte *data) { + int camera_channel = data[2] & 0x0F; + if (camera_channel >= NUM_TRIGGER_CHANNELS) + return; + + noInterrupts(); + + // in Level trigger mode, if the channel is already triggering, drop the new command to avoid overwriting the in-progress timing + if (trigger_mode != TRIGGER_MODE_NORMAL && + trigger_output_level[camera_channel] == LOW) { + interrupts(); + return; + } + + control_strobe[camera_channel] = (data[2] >> 7) & 0x01; + illumination_on_time_us[camera_channel] = + (uint32_t(data[3]) << 24) | (uint32_t(data[4]) << 16) | + (uint32_t(data[5]) << 8) | uint32_t(data[6]); + + // pull the trigger pin LOW (start of the negative pulse) + digitalWrite(camera_trigger_pins[camera_channel], LOW); + trigger_output_level[camera_channel] = LOW; + timestamp_trigger_rising_edge[camera_channel] = micros(); + + // reset the strobe state + strobe_on[camera_channel] = false; + + interrupts(); +} + +void CommandProcessor::handleSetStrobeDelay(const byte *data) { + int channel = data[2]; + if (channel >= NUM_TRIGGER_CHANNELS) + return; + strobe_delay_us[channel] = + (uint32_t(data[3]) << 24) | (uint32_t(data[4]) << 16) | + (uint32_t(data[5]) << 8) | uint32_t(data[6]); +} + +void CommandProcessor::handleSetAxisDisableEnable(const byte *data) { + // data[2]: protocol axis; data[3]: 0=disable, 1=enable + const char *name = protocolAxisToName(data[2]); + if (!name) return; + Axis *axis = axisManager.findAxisByName(name); + if (!axis) return; + if (data[3] == 0) axis->disableAxis(); + else axis->enableAxis(); +} + +void CommandProcessor::handleSetPinLevel(const byte *data) { + // defensive: if the pin requested by the host was not explicitly set OUTPUT in illumination_init, + // digitalWrite in INPUT mode does not change the actual level. Force OUTPUT on the first write. + pinMode(data[2], OUTPUT); + digitalWrite(data[2], data[3]); +} + +void CommandProcessor::handleInitFilterWheel(const byte *data) { + // 2026-05-26 fix a byte-level drop-in deviation: + // legacy Squid callback_initfilterwheel (commands.cpp:188-192) is an atomic operation: + // enable_filterwheel = true; + // init_filterwheel_axis(w); // chip reconfiguration only (SW_RESET + register writes) + // does **not** trigger homing, does **not** set mcu_cmd_execution_in_progress = true. + // the actual W homing is triggered separately by a subsequent home_w() (HOME_OR_ZERO + AXIS_W). + // + // bug history: previously axis->startHoming() here triggered W homing -> + // during legacy Squid software's init_filter_wheel(W) + sleep(0.5) + configure_squidfilter(W), + // octoaxes caused wait_till_operation_is_completed to time out 5s after set_leadscrew_pitch + // (during homing any_moving=true -> status=IN_PROGRESS, so wait is not woken). + // + // fix: no-op + log. The W axis is already configured in filter-wheel mode at axesmrg::beginAll startup + // (W_AXIS template), and the chip is already initialized at startup. A subsequent configure_squidfilter rewrites + // key registers such as microstep/current/VMAX/AMAX, so no re-initialization is needed here. + DEBUG_PRINTLN("INITFILTERWHEEL: no-op (W configured at startup; awaiting HOME_OR_ZERO for actual homing)"); +} + +void CommandProcessor::handleInitFilterWheelW2(const byte *data) { + // same as handleInitFilterWheel: legacy Squid callback_initfilterwheel_w2 (commands.cpp:194-198) + // only enable_filterwheel_w2=true + chip re-init, does not trigger homing. See the handleInitFilterWheel comment. + DEBUG_PRINTLN("INITFILTERWHEEL_W2: no-op (W2 configured at startup; awaiting HOME_OR_ZERO for actual homing)"); +} + +void CommandProcessor::handleInitialize(const byte *data) { + // matches legacy Squid behavior: cmd 254 INITIALIZE = equivalent to "power-cycle". + // legacy Squid writes RESET_REG=0x52535400 on the first line of tmc4361A_tmc2660_init to soft-reset the chip, + // then rewrites all configuration. This way, after the host restarts the GUI (chip not power-cycled), residual state such as XACTUAL/EVENTS/RAMPMODE + // is cleared, so cmd 9 SET_LIM and cmd 29 HOME can start from a clean state. + // + // equivalent to the SW_RESET = 0x52535400 on the first line of motor_initMotionController inside Axis::begin(). + // after beginAll, handleReset is called to reset the C++ software state machine (_currentState/_isMoving, etc.). + if (!axisManager.beginAll()) { + DEBUG_PRINTLN("INITIALIZE: beginAll FAILED"); + } + uint8_t count = axisManager.getAxisCount(); + for (uint8_t i = 0; i < count; i++) { + Axis *axis = axisManager.getAxis(i); + if (axis) axis->handleReset(); + } + // DAC + trigger reset + set_DAC8050x_config(); + set_DAC8050x_default_gain(); + trigger_mode = TRIGGER_MODE_NORMAL; + DEBUG_PRINTLN("INITIALIZE: chip SW_RESET + reconfig + state machine reset done"); +} + +void CommandProcessor::handleReset(const byte *data) { + // stop all axis motion, reset the trigger state + trigger_mode = TRIGGER_MODE_NORMAL; + uint8_t count = axisManager.getAxisCount(); + for (uint8_t i = 0; i < count; i++) { + Axis *axis = axisManager.getAxis(i); + if (axis) axis->handleReset(); + } + DEBUG_PRINTLN("RESET: all axes stopped, trigger_mode = 0"); +} diff --git a/firmware/octoaxes/commandprocessor.h b/firmware/octoaxes/commandprocessor.h new file mode 100644 index 000000000..f34ca0757 --- /dev/null +++ b/firmware/octoaxes/commandprocessor.h @@ -0,0 +1,68 @@ +#ifndef COMMAND_PROCESSOR_H +#define COMMAND_PROCESSOR_H + +#include + +class CommandProcessor { +public: + CommandProcessor(); + ~CommandProcessor(); + + // Command-handler function declarations + void handleMoveX(const byte* data); + void handleMoveY(const byte* data); + void handleMoveZ(const byte* data); + void handleMoveTheta(const byte* data); + void handleMoveW(const byte* data); + void handleHomeOrZero(const byte* data); + void handleMoveToX(const byte* data); + void handleMoveToY(const byte* data); + void handleMoveToZ(const byte* data); + void handleSetLim(const byte* data); + void handleTurnOnIllumination(const byte* data); + void handleTurnOffIllumination(const byte* data); + void handleSetIllumination(const byte* data); + void handleSetIlluminationLEDMatrix(const byte* data); + void handleAckJoystickButtonPressed(const byte* data); + void handleAnalogWriteOnboardDAC(const byte* data); + void handleSetDAC80508RefDivGain(const byte* data); + void handleSetIlluminationIntensityFactor(const byte* data); + void handleSetPortIntensity(const byte* data); + void handleTurnOnPort(const byte* data); + void handleTurnOffPort(const byte* data); + void handleSetPortIllumination(const byte* data); + void handleSetMultiPortMask(const byte* data); + void handleTurnOffAllPorts(const byte* data); + void handleMoveW2(const byte* data); + void handleMoveTurret(const byte* data); + void handleMoveToTurret(const byte* data); + void handleSetTriggerMode(const byte* data); + void handleMoveToW(const byte* data); + void handleSetLimSwitchPolarity(const byte* data); + void handleConfigureStepperDriver(const byte* data); + void handleSetMaxVelocityAcceleration(const byte* data); + void handleSetLeadScrewPitch(const byte* data); + void handleSetOffsetVelocity(const byte* data); + void handleConfigureStagePID(const byte* data); + void handleEnableStagePID(const byte* data); + void handleDisableStagePID(const byte* data); + void handleSetHomeSafetyMargin(const byte* data); + void handleSetPIDArguments(const byte* data); + void handleSendHardwareTrigger(const byte* data); + void handleSetStrobeDelay(const byte* data); + void handleSetAxisDisableEnable(const byte* data); + void handleSetWatchdogTimeout(const byte* data); + void handleSetPinLevel(const byte* data); + void handleHeartbeat(const byte* data); + void handleInitFilterWheel(const byte* data); + void handleInitFilterWheelW2(const byte* data); + void handleInitialize(const byte* data); + void handleReset(const byte* data); + +private: + // Private member variables (add as needed) +}; + +extern CommandProcessor commandProcessor; + +#endif diff --git a/firmware/octoaxes/config.h b/firmware/octoaxes/config.h new file mode 100644 index 000000000..fcdce735d --- /dev/null +++ b/firmware/octoaxes/config.h @@ -0,0 +1,573 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "axis.h" +#include "def_octopi_80120.h" + +namespace Commands { + const int MOVE_X = 0; + const int MOVE_Y = 1; + const int MOVE_Z = 2; + const int MOVE_THETA = 3; + const int MOVE_W = 4; + const int HOME_OR_ZERO = 5; + const int MOVETO_X = 6; + const int MOVETO_Y = 7; + const int MOVETO_Z = 8; + const int SET_LIM = 9; + const int TURN_ON_ILLUMINATION = 10; + const int TURN_OFF_ILLUMINATION = 11; + const int SET_ILLUMINATION = 12; + const int SET_ILLUMINATION_LED_MATRIX = 13; + const int ACK_JOYSTICK_BUTTON_PRESSED = 14; + const int ANALOG_WRITE_ONBOARD_DAC = 15; + const int SET_DAC80508_REFDIV_GAIN = 16; + const int SET_ILLUMINATION_INTENSITY_FACTOR = 17; + const int MOVETO_W = 18; + const int MOVE_W2 = 19; + const int SET_LIM_SWITCH_POLARITY = 20; + const int CONFIGURE_STEPPER_DRIVER = 21; + const int SET_MAX_VELOCITY_ACCELERATION = 22; + const int SET_LEAD_SCREW_PITCH = 23; + const int SET_OFFSET_VELOCITY = 24; + const int CONFIGURE_STAGE_PID = 25; + const int ENABLE_STAGE_PID = 26; + const int DISABLE_STAGE_PID = 27; + const int SET_HOME_SAFETY_MERGIN = 28; + const int SET_PID_ARGUMENTS = 29; + const int SEND_HARDWARE_TRIGGER = 30; + const int SET_STROBE_DELAY = 31; + const int SET_AXIS_DISABLE_ENABLE = 32; + const int SET_TRIGGER_MODE = 33; + // Multi-port illumination commands (v1.0+) + const int SET_PORT_INTENSITY = 34; + const int TURN_ON_PORT = 35; + const int TURN_OFF_PORT = 36; + const int SET_PORT_ILLUMINATION = 37; + const int SET_MULTI_PORT_MASK = 38; + const int TURN_OFF_ALL_PORTS = 39; + // Safety and heartbeat + const int SET_WATCHDOG_TIMEOUT = 40; // set the serial watchdog timeout (ms); once enabled, a communication loss automatically turns off the lights + const int SET_PIN_LEVEL = 41; + const int HEARTBEAT = 42; // no-op heartbeat (the watchdog is reset by received packets, not by this command) + // 2026-05-29 E1 objective-changer-specific motion commands (octoaxes extension; legacy Squid does not send them, so drop-in is preserved). + // MOVE_W/MOVETO_W are hardcoded to "W" with no axis index and cannot be reused; so, like W2, give E1 its own commands. + const int MOVE_TURRET = 44; // E1 relative move, data[2..5] = int32 microsteps big-endian + const int MOVETO_TURRET = 45; // E1 absolute move + const int INITFILTERWHEEL_W2 = 252; + const int INITFILTERWHEEL = 253; + const int INITIALIZE = 254; + const int RESET = 255; +} + +// Pin definitions +namespace Pins { + const int DAC8050x_CS = 33; + const int POWER_GOOD = 0; + const int TMC4361_STANDARD_CLK = 37; + const int TMC4361_EXPAND_CLK = 28; + + // Note: the X_AXIS_CS / Y_AXIS_CS constant names are legacy names based on the PCB pin labels, + // and do not directly correspond to the chips of the physical X/Y motors (the hardware wiring is determined by the + // axisName <-> CS pin mapping in octoaxes.ino, see the comment at octoaxes.ino:86-90). + const int X_AXIS_CS = 41; + const int Y_AXIS_CS = 36; + const int Z_AXIS_CS = 35; + const int W_AXIS_CS = 34; + + const int EXPAND1_AXIS_CS = 19; + const int EXPAND2_AXIS_CS = 18; + const int EXPAND3_AXIS_CS = 17; + const int EXPAND4_AXIS_CS = 16; + + // W2 (the second filter wheel) reuses the original EXPAND4 hardware: CS=pin 16, CLK=pin 28 (TMC4361_EXPAND_CLK). + // fully consistent with legacy Squid pin_TMC4361_CS[4]=16 / pin_TMC4361_CLK_W2=28. + const int W2_AXIS_CS = EXPAND4_AXIS_CS; + + // Control-pin arrays + const uint8_t CONTROL_PINS[] = {EXPAND1_AXIS_CS, EXPAND2_AXIS_CS, EXPAND3_AXIS_CS, EXPAND4_AXIS_CS}; + const uint8_t STANDARD_CONTROL_PINS[] = {W_AXIS_CS, Z_AXIS_CS, Y_AXIS_CS, X_AXIS_CS}; + const size_t NUM_CONTROL_PINS = 4; + const size_t NUM_STANDARD_CONTROL_PINS = 4; + + // Illumination TTL ports (D1-D5) + // Note: the D3/D4 pins are not in sequential order, consistent with the legacy light-source codes + const int ILLUMINATION_D1 = 5; + const int ILLUMINATION_D2 = 4; + const int ILLUMINATION_D3 = 22; + const int ILLUMINATION_D4 = 3; + const int ILLUMINATION_D5 = 23; + + // Laser safety interlock (LOW = safe) + const int ILLUMINATION_INTERLOCK = 2; + + // LED matrix (APA102, 128 pixels) + const int LED_MATRIX_DATA = 26; + const int LED_MATRIX_CLOCK = 27; + + // LED driver LT3932 SYNC (16 MHz PWM) + const int LED_DRIVER_SYNC = 25; + + // Camera trigger + const int CAMERA_TRIGGER_1 = 29; + const int CAMERA_TRIGGER_2 = 30; + const int CAMERA_TRIGGER_3 = 31; + const int CAMERA_TRIGGER_4 = 32; +} + +// System configuration +namespace SystemConfig { + const uint32_t TMC4361_CLOCK_FREQUENCY = 16000000; + const unsigned long LIMIT_CHECK_INTERVAL = 3000; +} + +// Axis constant definitions +namespace AxisConstDefinition { + const float R_sense_xy = 0.22; + const float R_sense_z = 0.43; + const float R_sense_objective = 0.22; + const float R_sense_filter = 0.1; + + const int FULLSTEPS_PER_REV_X = 200; + const int FULLSTEPS_PER_REV_Y = 200; + const int FULLSTEPS_PER_REV_Z = 200; + const int FULLSTEPS_PER_REV_FILTER = 200; + const int FULLSTEPS_PER_REV_OBJECTIVES = 200; + const int FULLSTEPS_PER_REV_THETA = 200; + + const float SCREW_PITCH_X_MM = 2.54; + const float SCREW_PITCH_Y_MM = 2.54; + const float SCREW_PITCH_Z_MM = 0.3; // conservative default for the old Z. The new Z (LE143S-W0601, 1mm pitch) is overridden by SET_LEAD_SCREW_PITCH sent at GUI startup (see software Z_AXIS_VARIANT) + const float SCREW_PITCH_FILTERWHEEL_MM = 1; // 2026-05-21 matches legacy Squid SCREW_PITCH_W_MM=1 (chip-side microstep semantics consistent with the GUI algorithm) + const float SCREW_PITCH_OBJECTIVES_MM = 1; + + const int MICROSTEPPING_X = 256; + const int MICROSTEPPING_Y = 256; + const int MICROSTEPPING_Z = 256; + const int MICROSTEPPING_FILTERWHEEL = 8; // 2026-05-26 path C speed optimization v2: 16->8 (BOW truncation further eased from 7x to 3.6x, matching the historically best microstep=8 config from 2026-02, physical floor ~70ms per slot) + const int MICROSTEPPING_OBJECTIVES = 64; + + // encoder resolution (um/pulse) + const float ENCODER_RESOLUTION_UM_X = 0.05; + const float ENCODER_RESOLUTION_UM_Y = 0.05; + const float ENCODER_RESOLUTION_UM_Z = 0.1; + + // Homing microstepping (default 256) + const int HOMING_MICROSTEPPING_X = 256; + const int HOMING_MICROSTEPPING_Y = 256; + const int HOMING_MICROSTEPPING_Z = 256; + const int HOMING_MICROSTEPPING_FILTERWHEEL = 256; + const int HOMING_MICROSTEPPING_OBJECTIVES = 256; + + // 2026-05-11 first speed-optimization round: matches legacy Squid HCS v2 config + // legacy Squid configuration_HCS_v2.ini: max_velocity_x/y/z_mm = 30/30/3.8 + // AMAX_Z 100 measured to actually increase Z 1mm time from 697->1569ms (+125%), suspected to be + // motor_adjustBows auto-computing too large a BOW + insufficient motor torque causing an abnormal ramp. + // keep the vmax increase, roll Z acceleration back to the original 20 mm/s2. + const float MAX_VELOCITY_X_mm = 30; + const float MAX_VELOCITY_Y_mm = 30; + const float MAX_VELOCITY_Z_mm = 3.8; + const float MAX_VELOCITY_FILTERWHEEL_mm = 4.2 * SCREW_PITCH_FILTERWHEEL_MM; + const float MAX_VELOCITY_OBJECTIVES_mm = 0.5 * SCREW_PITCH_OBJECTIVES_MM; + + const float MAX_ACCELERATION_X_mm = 500; + const float MAX_ACCELERATION_Y_mm = 500; + const float MAX_ACCELERATION_Z_mm = 20; + const float MAX_ACCELERATION_FILTERWHEEL_mm = 400 * SCREW_PITCH_FILTERWHEEL_MM; + // 2026-05-29 objectives branch: measured 200 mm/s2 with the weak 1A current loses steps badly, + // lowered to 80 mm/s2 to leave margin. Used together with EXPAND1_AXIS.currentRange=1 (2A) + motorCurrentMA=1800. + const float MAX_ACCELERATION_OBJECTIVES_mm = 80 * SCREW_PITCH_OBJECTIVES_MM; + + const float HOMING_VELOCITY_X_MM = 10; + const float HOMING_VELOCITY_Y_MM = 30; // 2026-05-12 measured: 256 microsteps + 30 mm/s is quietest + const float HOMING_VELOCITY_Z_MM = 1; // safe boot default = 1mm/s (old Z historical value, drop-in equivalent; legacy Squid has no channel to send the homing speed, so this default is all it can use). For the new Z, the octoaxes GUI sends S:SET_HOMING_VEL per variant at startup to raise it to 2mm/s (avoiding long-travel homing timeouts) + const float HOMING_VELOCITY_FILTERWHEEL_MM = 0.15 * SCREW_PITCH_FILTERWHEEL_MM; + const float HOMING_VELOCITY_OBJECTIVES_MM = 0.25 * SCREW_PITCH_OBJECTIVES_MM; + + // motor current setting (mA) -- peak current, not RMS + // TMC2660 formula: I_peak = (CS+1)/32 * V_FS/R_sense, I_rms = I_peak/sqrt(2) + // CS range 0~31, out-of-range is clamped, the actual peak is limited by R_sense + // chip absolute max: 4A peak (2.8A RMS) + const float X_MOTOR_PEAK_CURRENT_mA = 1000; // R=0.22ohm -> CS=9, actual 0.97A + const float Y_MOTOR_PEAK_CURRENT_mA = 1000; // R=0.22ohm -> CS=9, actual 0.97A + // 2026-06-03 newz branch: Z defaults to the conservative old value (500mA); the current of the new Z (LE143S-W0601, rated 1.5A) + // is overridden by CONFIGURE_STEPPER_DRIVER sent at GUI startup (see software Z_AXIS_VARIANT="new" -> 1500mA). + // This lets one firmware support both old and new Z boards: at the boot instant (before GUI config) the new motor gets only 500mA = weak but safe, avoiding overcurrent on the old motor. + // driver auto-detect (DRIVER_AUTO): old Z=TMC2660 uses R_sense; new Z=TMC2240 uses ICS+currentRange. + const float Z_MOTOR_PEAK_CURRENT_mA = 500; // conservative default R=0.43ohm -> CS=21, actual 0.47A (the new Z is raised to 1500mA by the GUI) + const float FILTERWHEEL_MOTOR_PEAK_CURRENT_mA = 3100; // R=0.10ohm -> CS=31 (max), actual 3.1A + // 2026-05-29 objectives branch: the weak 1A current loses steps with the gear-reduced objective. Raised to 1800mA. + // objective driver board R_sense=0.22ohm (only effective on the TMC2660 path; TMC2240 uses the integrated current sense ICS and ignores this resistor). + // EXPAND1_AXIS.driverType=DRIVER_AUTO auto-detects the chip on power-up, then selects the path: + // - TMC2240 (ICS): currentRange=1 -> I_FS=2A, IRUN=(1800/1000)/2*32-1=28 -> 1.81A peak + // - TMC2660 (R_S): r_sense=0.22ohm, 1800mA -> CS~=16 -> ~1.7A peak + // the two paths give similar current (~1.7-1.8A), enough torque for the gear-reduced objective. A driver board with R_sense != 0.22ohm requires recomputing CS. + const float OBJECTIVES_MOTOR_PEAK_CURRENT_mA = 1800; + + const float X_MOTOR_I_HOLD = 0.25; + const float Y_MOTOR_I_HOLD = 0.25; + const float Z_MOTOR_I_HOLD = 0.5; // conservative default (the new Z is raised to 0.75 by the GUI, to resist sag on the vertical axis) + const float FILTERWHEEL_MOTOR_I_HOLD = 0.5; + const float OBJECTIVES_MOTOR_I_HOLD = 0.5; + + const float X_SAFEMARGIN = 0.05; + const float Y_SAFEMARGIN = 0.05; + const float Z_SAFEMARGIN = 0.05; + const float FILTERWHEEL_SAFEMARGIN = 0.2; + const float OBJECTIVES_SAFEMARGIN = 0.004; + + const float X_SAFEPOSITION = 0.6; + const float Y_SAFEPOSITION = 0.6; + const float Z_SAFEPOSITION = 0.7; + const float FILTERWHEEL_SAFEPOSITION = 0; + const float OBJECTIVES_SAFEPOSITION = 0; +} + +// Illumination-system configuration +namespace IlluminationConfig { + // DAC80508 register addresses + const uint8_t DAC_CONFIG_ADDR = 0x03; + const uint8_t DAC_GAIN_ADDR = 0x04; + const uint8_t DAC_DAC_ADDR = 0x08; + + // default DAC gain: div=0x00, gains=0x80 (channels 0-6 gain 1, channel 7 gain 2) + const uint8_t DAC_DEFAULT_DIV = 0x00; + const uint8_t DAC_DEFAULT_GAINS = 0x80; + + // LED matrix (APA102, 128 pixels, BGR order) + const int NUM_LEDS = 128; + const int LED_MAX_INTENSITY = 100; + const float GREEN_ADJUSTMENT = 1.0f; + const float RED_ADJUSTMENT = 1.0f; + const float BLUE_ADJUSTMENT = 1.0f; + + // default global intensity factor (Squid LED 0-1.5V) + const float DEFAULT_INTENSITY_FACTOR = 0.6f; + + // number of ports (D1-D16) + const int NUM_PORTS = 16; + + // illumination light-source codes (legacy API, kept consistent with the protocol) + // LED matrix patterns: 0-8 + const int LED_ARRAY_FULL = 0; + const int LED_ARRAY_LEFT_HALF = 1; + const int LED_ARRAY_RIGHT_HALF = 2; + const int LED_ARRAY_LEFTB_RIGHTR = 3; + const int LED_ARRAY_LOW_NA = 4; + const int LED_ARRAY_LEFT_DOT = 5; + const int LED_ARRAY_RIGHT_DOT = 6; + const int LED_ARRAY_TOP_HALF = 7; + const int LED_ARRAY_BOTTOM_HALF = 8; + const int LED_EXTERNAL_FET = 20; + // TTL-port light-source codes (note: D3/D4 are out of order!) + const int D1 = 11; + const int D2 = 12; + const int D3 = 14; // out of order! + const int D4 = 13; // out of order! + const int D5 = 15; +} + +// Axis configuration +namespace AxisConfigs { + + // X-axis configuration + const Axis::AxisConfig X_AXIS = { + .clockFrequency = SystemConfig::TMC4361_CLOCK_FREQUENCY, + .homingSwitch = LEFT_SW, + .leftSwitchPolarity = 0, + .rightSwitchPolarity = 0, + .leftIsInactive = 0, + .rightIsInactive = 0, + .leftFlipped = true, + .rightFlipped = true, + .enableLeftLimitSwitch = true, + .enableRightLimitSwitch = true, + .r_sense = AxisConstDefinition::R_sense_xy, + .screwPitchMM = AxisConstDefinition::SCREW_PITCH_X_MM, + .fullStepsPerRev = AxisConstDefinition::FULLSTEPS_PER_REV_X, + .microstepping = AxisConstDefinition::MICROSTEPPING_X, + .homingMicrostepping = AxisConstDefinition::HOMING_MICROSTEPPING_X, + .maxVelocityMM = AxisConstDefinition::MAX_VELOCITY_X_mm, + .maxAccelerationMM = AxisConstDefinition::MAX_ACCELERATION_X_mm, + .homingVelocityMM = AxisConstDefinition::HOMING_VELOCITY_X_MM, + .motorCurrentMA = AxisConstDefinition::X_MOTOR_PEAK_CURRENT_mA, + .holdCurrent = AxisConstDefinition::X_MOTOR_I_HOLD, + .homeSafetyMarginMM = AxisConstDefinition::X_SAFEMARGIN, + .homeSafetyPositionMM = AxisConstDefinition::X_SAFEPOSITION, + // StallGuard parameters (only used by TMC2660 SG2; TMC2240 SG4 is + // temporarily skipped where it is enabled in axis.cpp, parameters kept to enable after SG4 tuning) + .enableStallSensitivity = true, + .stallSensitivity = 12, + .useSShapedRamp = true, + .astartMM = 0, + .dfinalMM = 0, + .homing_timeout_ms = 30000, + .homing_direct = -1, + .driverType = DRIVER_AUTO, + .currentRange = 0, + .enableEncoder = false, + .encoderLinesPerRev = (uint16_t)(AxisConstDefinition::SCREW_PITCH_X_MM * 1000 / AxisConstDefinition::ENCODER_RESOLUTION_UM_X), + .invertEncoderDir = false, + .invert_direction = false // 2026-05-25 hardware direction inversion, default false + }; + + // Y-axis configuration + const Axis::AxisConfig Y_AXIS = { + .clockFrequency = SystemConfig::TMC4361_CLOCK_FREQUENCY, + .homingSwitch = LEFT_SW, + .leftSwitchPolarity = 0, + .rightSwitchPolarity = 0, + .leftIsInactive = 0, + .rightIsInactive = 0, + .leftFlipped = true, + .rightFlipped = true, + .enableLeftLimitSwitch = true, + .enableRightLimitSwitch = true, + .r_sense = AxisConstDefinition::R_sense_xy, + .screwPitchMM = AxisConstDefinition::SCREW_PITCH_Y_MM, + .fullStepsPerRev = AxisConstDefinition::FULLSTEPS_PER_REV_Y, + .microstepping = AxisConstDefinition::MICROSTEPPING_Y, + .homingMicrostepping = AxisConstDefinition::HOMING_MICROSTEPPING_Y, + .maxVelocityMM = AxisConstDefinition::MAX_VELOCITY_Y_mm, + .maxAccelerationMM = AxisConstDefinition::MAX_ACCELERATION_Y_mm, + .homingVelocityMM = AxisConstDefinition::HOMING_VELOCITY_Y_MM, + .motorCurrentMA = AxisConstDefinition::Y_MOTOR_PEAK_CURRENT_mA, + .holdCurrent = AxisConstDefinition::Y_MOTOR_I_HOLD, + .homeSafetyMarginMM = AxisConstDefinition::Y_SAFEMARGIN, + .homeSafetyPositionMM = AxisConstDefinition::Y_SAFEPOSITION, + // same as X: StallGuard parameters only used by TMC2660; TMC2240 is skipped where enabled + .enableStallSensitivity = true, + .stallSensitivity = 12, + .useSShapedRamp = true, + .astartMM = 0, + .dfinalMM = 0, + .homing_timeout_ms = 40000, + .homing_direct = -1, + .driverType = DRIVER_AUTO, + .currentRange = 0, + .enableEncoder = false, + .encoderLinesPerRev = (uint16_t)(AxisConstDefinition::SCREW_PITCH_Y_MM * 1000 / AxisConstDefinition::ENCODER_RESOLUTION_UM_Y), + .invertEncoderDir = false, + .invert_direction = false // 2026-05-25 hardware direction inversion, default false + }; + + // Z-axis configuration + // ─────────────────────────────────────────────────────────────────── + // * Z-variant software switch: switching old/new Z only changes one line, Z_AXIS_VARIANT in software/octoaxes/constants.py + // (the GUI sends pitch/current/microstepping at startup + limit polarity via cmd 20); [no firmware reflash needed, no compile switch needed]. + // After the positive/negative limit sensors were physically swapped on 06-09, the only firmware-side difference between old/new Z = limit polarity (new=1/old=0), which + // is sent by the host via cmd 20 (SET_LIM_SWITCH_POLARITY) and overridden by reapplyLimitSwitches() re-writing the chip, + // so the original #define Z_VARIANT_NEW compile switch was removed (2026-06-09). The fields below are the "boot-window defaults" + // (effective before GUI config, overridden by what is sent afterward); homingSwitch/flip/enable/invertEncoder values for old/new Z + // are already identical; only the polarity needs software differentiation, so the new default value 1 is used here. + // pitch/current/microstepping are overridden by the GUI; currentRange=1 is common to both boards. + // (WARNING) the new-Z limit behavior on the octoaxes mainline board has not yet been tested on that board (different connector/wiring) -- if testing finds homingSwitch/ + // flip must differ from these defaults, verify with software/common/tests/z_limit_monitor.py before adjusting (see the Turret cautionary example). + const Axis::AxisConfig Z_AXIS = { + .clockFrequency = SystemConfig::TMC4361_CLOCK_FREQUENCY, + .homingSwitch = RGHT_SW, // boot default (both old/new Z use RGHT_SW; after the 06-09 sensor swap, home connects to the STOPR pin without flipping and is read directly as the STOPR bit) + .leftSwitchPolarity = 0, // boot default = old Z (0, active-low) -- the octoaxes mainline has the old Z installed, so the firmware must default to supporting the old Z; the new Z (1) is switched via cmd 20 sent at GUI startup + .rightSwitchPolarity = 0, + .polarityAffectsChip = true, // Z only: allow cmd 20 to write the polarity to the chip (Z-variant software switch; the new Z sends 1 to override the boot default 0); X/Y etc. omit it = false, not writing the chip, preserving legacy Squid drop-in + .leftIsInactive = 0, + .rightIsInactive = 0, + .leftFlipped = false, // false for both old/new Z (the 06-09 sensor swap cancels the coordinate inversion, so INVERT_STOP_DIRECTION is not needed) + .rightFlipped = false, + .enableLeftLimitSwitch = true, // true for both old/new Z (the chip's upper/lower hard stops work fine) + .enableRightLimitSwitch = true, + .r_sense = AxisConstDefinition::R_sense_z, + .screwPitchMM = AxisConstDefinition::SCREW_PITCH_Z_MM, + .fullStepsPerRev = AxisConstDefinition::FULLSTEPS_PER_REV_Z, + .microstepping = AxisConstDefinition::MICROSTEPPING_Z, + .homingMicrostepping = AxisConstDefinition::HOMING_MICROSTEPPING_Z, + .maxVelocityMM = AxisConstDefinition::MAX_VELOCITY_Z_mm, + .maxAccelerationMM = AxisConstDefinition::MAX_ACCELERATION_Z_mm, + .homingVelocityMM = AxisConstDefinition::HOMING_VELOCITY_Z_MM, + .motorCurrentMA = AxisConstDefinition::Z_MOTOR_PEAK_CURRENT_mA, + .holdCurrent = AxisConstDefinition::Z_MOTOR_I_HOLD, + .homeSafetyMarginMM = AxisConstDefinition::Z_SAFEMARGIN, + .homeSafetyPositionMM = AxisConstDefinition::Z_SAFEPOSITION, + .enableStallSensitivity = false, + .stallSensitivity = 6, + .useSShapedRamp = true, + .astartMM = 0, + .dfinalMM = 0, + .homing_timeout_ms = 60000, // 60s: leaves ample margin for new-Z + legacy-Squid (can only use the default 1mm/s, ~34.5mm travel, ~34.5s worst case). Increasing the timeout has no side effects + .homing_direct = 1, + .driverType = DRIVER_AUTO, + .currentRange = 1, // 2026-06-03 newz: TMC2240 ICS I_FS=2A (needed for the new Z's 1.5A). Safe for both Z boards: old Z=TMC2660 ignores this field (uses R_sense), new Z=TMC2240 uses it -> one firmware fits both + .enableEncoder = false, + .encoderLinesPerRev = (uint16_t)(AxisConstDefinition::SCREW_PITCH_Z_MM * 1000 / AxisConstDefinition::ENCODER_RESOLUTION_UM_Z), + .invertEncoderDir = true, // boot default (ENC-3, not effective while enableEncoder=false); at runtime overridden by GUI CONFIGURE_STAGE_PID per constants.py encoder_flip_direction + .invert_direction = false // 2026-05-25 hardware direction inversion, default false + }; + + // W axis 4 configuration (filter wheel) + // 2026-05-26 W .invert_direction reverted to false: makes octoaxes firmware byte-level identical to legacy Squid firmware. + // the old decision (set true on 2026-05-25) intended to correct home+offset to land at the center of slot 1 after this hardware's mirror assembly, + // but at the cost of also inverting the physical direction of all MOVE_W (next/previous) and MOVETO_W, inconsistent with legacy Squid, + // violating the CLAUDE.md "byte-level drop-in replacement" goal. + // now reverted to byte-level consistency: the home+offset physical position is exactly the same as legacy Squid (+2.87 deg on your hardware, + // not centered on slot 1 -- this is legacy Squid's inherent behavior on this hardware, caused by the hardware mirror assembly, + // not an octoaxes bug). Precise slot-1 alignment requires a hardware-level fix (reassembling the wheel). + const Axis::AxisConfig W_AXIS = { + .clockFrequency = SystemConfig::TMC4361_CLOCK_FREQUENCY, + .homingSwitch = LEFT_SW, + .leftSwitchPolarity = 0, + .rightSwitchPolarity = 0, + .leftIsInactive = 0, + .rightIsInactive = 0, + .leftFlipped = false, + .rightFlipped = false, + .enableLeftLimitSwitch = true, + .enableRightLimitSwitch = false, + .r_sense = AxisConstDefinition::R_sense_filter, + .screwPitchMM = AxisConstDefinition::SCREW_PITCH_FILTERWHEEL_MM, + .fullStepsPerRev = AxisConstDefinition::FULLSTEPS_PER_REV_FILTER, + .microstepping = AxisConstDefinition::MICROSTEPPING_FILTERWHEEL, + .homingMicrostepping = AxisConstDefinition::HOMING_MICROSTEPPING_FILTERWHEEL, + .maxVelocityMM = AxisConstDefinition::MAX_VELOCITY_FILTERWHEEL_mm, + .maxAccelerationMM = AxisConstDefinition::MAX_ACCELERATION_FILTERWHEEL_mm, + .homingVelocityMM = AxisConstDefinition::HOMING_VELOCITY_FILTERWHEEL_MM, + .motorCurrentMA = AxisConstDefinition::FILTERWHEEL_MOTOR_PEAK_CURRENT_mA, + .holdCurrent = AxisConstDefinition::FILTERWHEEL_MOTOR_I_HOLD, + .homeSafetyMarginMM = AxisConstDefinition::FILTERWHEEL_SAFEMARGIN, + .homeSafetyPositionMM = AxisConstDefinition::FILTERWHEEL_SAFEPOSITION, + .enableStallSensitivity = false, + .stallSensitivity = 6, + .useSShapedRamp = true, + .astartMM = 22.5f * AxisConstDefinition::SCREW_PITCH_FILTERWHEEL_MM, // 2026-05-26 path C v2: ASTART=22.5 rev/s2, equivalent to the historically best chip register value 288,000 ustep/s2 in the microstep=8 era (history: 180 rev/s2 * 1600 ustep/rev = 288K; now needs 22.5 * 12800 = 288K). Avoids overshoot at short distances, still gains jerk-start acceleration at long distances. + .dfinalMM = 0, // same as astart + .homing_timeout_ms = 80000, + .homing_direct = 1, + .driverType = DRIVER_AUTO, + .currentRange = 2, + .enableEncoder = false, + .encoderLinesPerRev = 4000, + .invertEncoderDir = false, + .invert_direction = false // 2026-05-26 reverted to byte-level drop-in: physical direction consistent with legacy Squid firmware (see the comment above this struct) + }; + + // Expansion axis 1 configuration (objectives turret) + // 2026-05-29 ported from the objectives branch after on-hardware testing: this board's objective home sensor is physically connected to the TMC4361A's + // RIGHT input pin (verified with dump_axis_state.py: at home STOPR_ACTIVE_F=1 / leaving=0). + // so homingSwitch=RGHT_SW, enableRight=true, enableLeft=false. + // Objectives::performHomingSequence was changed to dynamically use _config.homingSwitch (no longer hardcoding OBSW_SW). + const Axis::AxisConfig EXPAND1_AXIS = { + .clockFrequency = SystemConfig::TMC4361_CLOCK_FREQUENCY, + .homingSwitch = RGHT_SW, + .leftSwitchPolarity = 0, + .rightSwitchPolarity = 0, + .leftIsInactive = 1, + .rightIsInactive = 1, + .leftFlipped = false, + .rightFlipped = false, + .enableLeftLimitSwitch = false, + .enableRightLimitSwitch = true, + .r_sense = AxisConstDefinition::R_sense_objective, + .screwPitchMM = AxisConstDefinition::SCREW_PITCH_OBJECTIVES_MM, + .fullStepsPerRev = AxisConstDefinition::FULLSTEPS_PER_REV_OBJECTIVES, + .microstepping = AxisConstDefinition::MICROSTEPPING_OBJECTIVES, + .homingMicrostepping = AxisConstDefinition::HOMING_MICROSTEPPING_OBJECTIVES, + .maxVelocityMM = AxisConstDefinition::MAX_VELOCITY_OBJECTIVES_mm, + .maxAccelerationMM = AxisConstDefinition::MAX_ACCELERATION_OBJECTIVES_mm, + .homingVelocityMM = AxisConstDefinition::HOMING_VELOCITY_OBJECTIVES_MM, + .motorCurrentMA = AxisConstDefinition::OBJECTIVES_MOTOR_PEAK_CURRENT_mA, + .holdCurrent = AxisConstDefinition::OBJECTIVES_MOTOR_I_HOLD, + .homeSafetyMarginMM = AxisConstDefinition::OBJECTIVES_SAFEMARGIN, + .homeSafetyPositionMM = AxisConstDefinition::OBJECTIVES_SAFEPOSITION, + .enableStallSensitivity = false, + .stallSensitivity = 15, + .useSShapedRamp = true, + .astartMM = 0, + .dfinalMM = 0, + .homing_timeout_ms = 80000, + .homing_direct = 1, + .driverType = DRIVER_AUTO, + .currentRange = 1, // 2026-05-29 TMC2240 I_FS=2A (the original 0=1A lost steps with the gear-reduced objective) + .enableEncoder = false, + .encoderLinesPerRev = 0, + .invertEncoderDir = false, + .invert_direction = false // 2026-05-25 hardware direction inversion, default false + }; + + // Expansion axis 3 configuration (Z-axis configuration) + const Axis::AxisConfig EXPAND3_AXIS = { + .clockFrequency = SystemConfig::TMC4361_CLOCK_FREQUENCY, + .homingSwitch = RGHT_SW, + .leftSwitchPolarity = 0, + .rightSwitchPolarity = 0, + .leftIsInactive = 0, + .rightIsInactive = 0, + .leftFlipped = false, + .rightFlipped = false, + .enableLeftLimitSwitch = true, + .enableRightLimitSwitch = true, + .r_sense = AxisConstDefinition::R_sense_z, + .screwPitchMM = AxisConstDefinition::SCREW_PITCH_Z_MM, + .fullStepsPerRev = AxisConstDefinition::FULLSTEPS_PER_REV_Z, + .microstepping = AxisConstDefinition::MICROSTEPPING_Z, + .homingMicrostepping = AxisConstDefinition::HOMING_MICROSTEPPING_Z, + .maxVelocityMM = AxisConstDefinition::MAX_VELOCITY_Z_mm, + .maxAccelerationMM = AxisConstDefinition::MAX_ACCELERATION_Z_mm, + .homingVelocityMM = AxisConstDefinition::HOMING_VELOCITY_Z_MM, + .motorCurrentMA = AxisConstDefinition::Z_MOTOR_PEAK_CURRENT_mA, + .holdCurrent = AxisConstDefinition::Z_MOTOR_I_HOLD, + .homeSafetyMarginMM = AxisConstDefinition::Z_SAFEMARGIN, + .homeSafetyPositionMM = AxisConstDefinition::Z_SAFEPOSITION, + .enableStallSensitivity = false, + .stallSensitivity = 6, + .useSShapedRamp = true, + .astartMM = 0, + .dfinalMM = 0, + .homing_timeout_ms = 20000, + .homing_direct = 1, + .driverType = DRIVER_AUTO, + .currentRange = 1, // audit F-8: unified to 1 with Z_AXIS. EXPAND3 reuses the Z template; if a 1.5A new Z (TMC2240 I_FS=2A) is connected, this value is needed for correct current; old Z TMC2660 ignores this field, safe. EXPAND3 is currently not instantiated + .enableEncoder = false, + .encoderLinesPerRev = 0, + .invertEncoderDir = false, + .invert_direction = false // 2026-05-25 hardware direction inversion, default false + }; + + // Expansion axis 4 configuration (filter wheel) + const Axis::AxisConfig EXPAND4_AXIS = { + .clockFrequency = SystemConfig::TMC4361_CLOCK_FREQUENCY, + .homingSwitch = LEFT_SW, + .leftSwitchPolarity = 0, + .rightSwitchPolarity = 0, + .leftIsInactive = 0, + .rightIsInactive = 0, + .leftFlipped = false, + .rightFlipped = false, + .enableLeftLimitSwitch = true, + .enableRightLimitSwitch = false, + .r_sense = AxisConstDefinition::R_sense_filter, + .screwPitchMM = AxisConstDefinition::SCREW_PITCH_FILTERWHEEL_MM, + .fullStepsPerRev = AxisConstDefinition::FULLSTEPS_PER_REV_FILTER, + .microstepping = AxisConstDefinition::MICROSTEPPING_FILTERWHEEL, + .homingMicrostepping = AxisConstDefinition::HOMING_MICROSTEPPING_FILTERWHEEL, + .maxVelocityMM = AxisConstDefinition::MAX_VELOCITY_FILTERWHEEL_mm, + .maxAccelerationMM = AxisConstDefinition::MAX_ACCELERATION_FILTERWHEEL_mm, + .homingVelocityMM = AxisConstDefinition::HOMING_VELOCITY_FILTERWHEEL_MM, + .motorCurrentMA = AxisConstDefinition::FILTERWHEEL_MOTOR_PEAK_CURRENT_mA, + .holdCurrent = AxisConstDefinition::FILTERWHEEL_MOTOR_I_HOLD, + .homeSafetyMarginMM = AxisConstDefinition::FILTERWHEEL_SAFEMARGIN, + .homeSafetyPositionMM = AxisConstDefinition::FILTERWHEEL_SAFEPOSITION, + .enableStallSensitivity = false, + .stallSensitivity = 6, + .useSShapedRamp = true, + .astartMM = 22.5f * AxisConstDefinition::SCREW_PITCH_FILTERWHEEL_MM, // 2026-05-26 path C v2: W2 same as W (22.5 rev/s2 ~= 288K ustep/s2 chip register, see the W_AXIS comment) + .dfinalMM = 0, + .homing_timeout_ms = 80000, + .homing_direct = 1, + .driverType = DRIVER_AUTO, + .currentRange = 0, + .enableEncoder = false, + .encoderLinesPerRev = 0, + .invertEncoderDir = false, + .invert_direction = false // 2026-05-26 W2 same as W, reverted to byte-level drop-in (see the comment above W_AXIS) + }; +} + +#endif diff --git a/firmware/octoaxes/def_octopi_80120.h b/firmware/octoaxes/def_octopi_80120.h new file mode 100644 index 000000000..30faf1f4d --- /dev/null +++ b/firmware/octoaxes/def_octopi_80120.h @@ -0,0 +1,49 @@ +#ifndef DEF_OCTOPI_80120_H +#define DEF_OCTOPI_80120_H + +// LED matrix +#define DOTSTAR_NUM_LEDS 128 + +// Axis assignment +static const uint8_t x = 1; +static const uint8_t y = 0; +static const uint8_t z = 2; +static const uint8_t w = 3; + +// limit switch +static const bool flip_limit_switch_x = true; +static const bool flip_limit_switch_y = true; + +// Motorized stage +static const long X_NEG_LIMIT_MM = -130; +static const long X_POS_LIMIT_MM = 130; +static const long Y_NEG_LIMIT_MM = -130; +static const long Y_POS_LIMIT_MM = 130; +static const long Z_NEG_LIMIT_MM = -20; +static const long Z_POS_LIMIT_MM = 20; + +// encoder +static const bool X_use_encoder = false; +static const bool Y_use_encoder = false; +static const bool Z_use_encoder = false; +static const bool W_use_encoder = false; + +// signs +static const int MOVEMENT_SIGN_X = 1; // not used for now +static const int MOVEMENT_SIGN_Y = 1; // not used for now +static const int MOVEMENT_SIGN_Z = 1; // not used for now +static const int ENCODER_SIGN_X = 1; // not used for now +static const int ENCODER_SIGN_Y = 1; // not used for now +static const int ENCODER_SIGN_Z = 1; // not used for now +static const int JOYSTICK_SIGN_X = -1; +static const int JOYSTICK_SIGN_Y = 1; +static const int JOYSTICK_SIGN_Z = 1; + +// limit switch polarity +static const bool LIM_SWITCH_X_ACTIVE_LOW = false; +static const bool LIM_SWITCH_Y_ACTIVE_LOW = false; +static const bool LIM_SWITCH_Z_ACTIVE_LOW = false; + +// offset velocity enable/disable +static const bool enable_offset_velocity = false; +#endif diff --git a/firmware/octoaxes/download.sh b/firmware/octoaxes/download.sh new file mode 100755 index 000000000..8625205ea --- /dev/null +++ b/firmware/octoaxes/download.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Octoaxes main-controller firmware flashing script +# ./download.sh interactive selection +# ./download.sh safe enable the laser interlock (pin 2 must be wired to the interlock signal; standard factory build) +# ./download.sh nointerlock disable the laser interlock (for laser-free stations, otherwise the D1-D5 fluorescence channels will not light) +set -e +cd "$(dirname "$0")" + +choice="${1:-}" +if [ -z "$choice" ]; then + echo "Select the build to flash:" + echo " 1) interlock enabled (safe) - pin 2 must be wired to the laser interlock signal to enable the D1-D5 TTL ports" + echo " 2) interlock disabled (nointerlock) - skips the interlock check, for laser-free stations; the LED matrix is unaffected" + read -rp "Enter 1 or 2: " ans + case "$ans" in + 1|safe|SAFE) choice=safe ;; + 2|nointerlock|NOINTERLOCK) choice=nointerlock ;; + *) echo "Invalid choice: $ans" >&2; exit 1 ;; + esac +fi + +case "$choice" in + safe) env=teensy41 ;; + nointerlock) env=teensy41_nointerlock ;; + *) echo "Unknown build: $choice (allowed: safe | nointerlock)" >&2; exit 1 ;; +esac + +echo ">>> flashing env=$env" +pio run -e "$env" -t upload diff --git a/firmware/octoaxes/filterwheel.cpp b/firmware/octoaxes/filterwheel.cpp new file mode 100644 index 000000000..6dab12c54 --- /dev/null +++ b/firmware/octoaxes/filterwheel.cpp @@ -0,0 +1,270 @@ +#include "filterwheel.h" +#include "build_opt.h" + +FilterWheel::FilterWheel(uint8_t csPin, uint8_t axisIndex, const char* axisName, uint8_t filterCount) + : Axis(csPin, axisIndex, axisName), _filterCount(filterCount), _currentFilter(0) { + _filterPositions = new float[filterCount]; + + // Initialize default positions: evenly spaced, assuming each filter is 60 degrees apart + for (uint8_t i = 0; i < filterCount; i++) { + _filterPositions[i] = i * (360.0f / filterCount); // in degrees; must be converted to mm when actually used + } +} + +bool FilterWheel::begin(const AxisConfig& config) { + // call the base-class init + bool result = Axis::begin(config); + + if (result) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":FilterWheel with "); + DEBUG_PRINT(_filterCount); + DEBUG_PRINTLN(" filters initialized successfully"); + } + + return result; +} + +bool FilterWheel::moveToFilter(uint8_t filterPosition) { + if (!isValidFilterPosition(filterPosition)) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Invalid filter position: "); + DEBUG_PRINTLN(filterPosition); + return false; + } + + if (_currentState != STATE_IDLE) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Filter wheel is busy"); + return false; + } + + float targetPosition = getFilterPosition(filterPosition); + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Moving to filter "); + DEBUG_PRINT(filterPosition); + DEBUG_PRINT(" at position "); + DEBUG_PRINTLN(targetPosition); + + if (Axis::moveToPosition(targetPosition)) { + _currentFilter = filterPosition; + return true; + } + + return false; +} + +uint8_t FilterWheel::getCurrentFilter() const { + return _currentFilter; +} + +uint8_t FilterWheel::getFilterCount() const { + return _filterCount; +} + +void FilterWheel::update() { + // call the base-class update first + Axis::update(); + + // filter-wheel-specific update logic can be added here + // e.g. check whether the target filter position has been reached +} + +bool FilterWheel::processCommand(const String& command) { + if (command.startsWith("MOVE_TO_FILTER")) { + return handleMoveToFilter(command); + } else if (command.startsWith("GET_CURRENT_FILTER")) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":CURRENT_FILTER:"); + DEBUG_PRINTLN(_currentFilter); + return true; + } else if (command.startsWith("GET_FILTER_COUNT")) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":FILTER_COUNT:"); + DEBUG_PRINTLN(_filterCount); + return true; + } else { + // hand other commands to the base class + return Axis::processCommand(command); + } +} + +void FilterWheel::setFilterPositions(const float* positions, uint8_t count) { + if (count > _filterCount) { + count = _filterCount; + } + + for (uint8_t i = 0; i < count; i++) { + _filterPositions[i] = positions[i]; + } + + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Filter positions updated"); +} + +bool FilterWheel::handleMoveToFilter(const String& command) { + int space1 = command.indexOf(' '); + if (space1 == -1) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":MOVE_TO_FILTER ERROR: Invalid format"); + return false; + } + + String filterStr = command.substring(space1 + 1); + uint8_t filterPosition = (uint8_t)filterStr.toInt(); + + if (!moveToFilter(filterPosition)) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":MOVE_TO_FILTER ERROR: Movement failed"); + return false; + } + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":MOVE_TO_FILTER: Moving to filter "); + DEBUG_PRINTLN(filterPosition); + return true; +} + +float FilterWheel::getFilterPosition(uint8_t filterIndex) const { + if (filterIndex < _filterCount) { + return _filterPositions[filterIndex]; + } + return 0.0f; +} + +bool FilterWheel::isValidFilterPosition(uint8_t filterPosition) const { + return (filterPosition < _filterCount); +} + +void FilterWheel::performHomingSequence() { + if (checkTimeout(_homing_timeout_ms)) { + restoreNormalMicrosteps(); + handleError("Homing timeout"); + return; + } + + uint8_t limit_state = readLimitSwitches(); + + switch (_currentState) { + case STATE_HOMING_INIT: + // directly disable the virtual limits in hardware without changing the _softLimitsEnabled flag + motor_enableSoftLimits(_icID, false, false); + _slowApproach = false; + switchToHomingMicrosteps(); + + if (limit_state == 0x00) { + // already in the sensing zone, move out first + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Already at home, moving away first..."); + setState(STATE_LEAVING_HOME); + } else { + // not in the sensing zone, fast search + // 2026-05-25 reverted commit 2b5dce4's "direction bug fix", restoring behavior consistent with the legacy Squid W section: + // hardcoded + directional search (legacy Squid stage_commands.cpp:621-636: when W HOME_NEGATIVE is not in the + // sensing zone it moves toward RGHT_DIR, which is W-section-specific behavior, opposite to X/Y/Z). + // hardware direction inversion is handled via _config.invert_direction (set true for mirror-assembled hardware). + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Fast search..."); + int32_t speedInternal = motor_velocityMMToInternal(_icID, _config.homingVelocityMM); + if (_config.invert_direction) speedInternal = -speedInternal; + motor_setVelocityInternal(_icID, speedInternal); + setState(STATE_HOMING_SEARCH); + } + break; + + case STATE_HOMING_SEARCH: + if (limit_state == 0x00) { + // reached the sensing zone + motor_setVelocityInternal(_icID, 0); // stop + delay(100); + + if (!_slowApproach) { + // phase one (fast): after finding the sensing zone, move out then approach slowly + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Sensor found (fast), moving away for slow approach..."); + _slowApproach = true; + setState(STATE_LEAVING_HOME); + } else { + // phase two (slow): precise positioning done + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Sensor found (slow), homing position locked."); + + // stop and zero first, then switch back to position mode, finally restore microstepping and motion parameters + motor_setCurrentPositionMicrosteps(_icID, 0); // VMAX=0 stop, set zero, velocity_mode=true + motor_moveToMicrosteps(_icID, 0); // trigger sRampInit to switch back to position mode (target=0=current, no movement) + restoreNormalMicrosteps(); // safely restore microstepping and VMAX/AMAX + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Homing completed! Current position set to 0"); + + // after homing completes, restore soft limits and PID + if (_softLimitsEnabled) { + enableSoftLimits(true); + } + if (_pidState.enabled) { + motor_enablePID(_icID); + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":PID re-enabled after homing"); + } + + setState(STATE_IDLE); + } + } + break; + + default: + break; + } +} + +void FilterWheel::performLeavingHome() { + if (checkTimeout(LEAVING_HOME_TIMEOUT_MS)) { + handleError("Leaving home timeout"); + return; + } + + uint8_t limit_state = readLimitSwitches(); + + if (_currentState == STATE_LEAVING_HOME) { + if (!(limit_state == 0x00)) { + // has left the sensing zone + DEBUG_PRINT(_axisName); + + // 2026-05-25 reverted commit 2b5dce4: restored hardcoded + directional search (consistent with the legacy Squid W section). + // leave direction = -search direction (original logic choosing one of two based on homingSwitch). + // hardware inversion is handled uniformly by _config.invert_direction. + if (_slowApproach) { + // stop first to ensure a consistent slow-approach start point + motor_setVelocityInternal(_icID, 0); + delay(100); + DEBUG_PRINTLN(":Left sensor, slow approach..."); + int32_t speedInternal = motor_velocityMMToInternal(_icID, _config.homingVelocityMM / 5.0); + if (_config.invert_direction) speedInternal = -speedInternal; + motor_setVelocityInternal(_icID, speedInternal); + } else { + // fast search for the sensing zone + DEBUG_PRINTLN(":Left sensor, fast search..."); + int32_t speedInternal = motor_velocityMMToInternal(_icID, _config.homingVelocityMM); + if (_config.invert_direction) speedInternal = -speedInternal; + motor_setVelocityInternal(_icID, speedInternal); + } + setState(STATE_HOMING_SEARCH); + } else { + // still in the sensing zone, keep moving out (original legacy Squid logic: choose one of two based on homingSwitch) + float leaveSpeed = _slowApproach + ? _config.homingVelocityMM / 5.0 // move out slowly to reduce overshoot + : _config.homingVelocityMM; // move out at full speed + int32_t speedInternal; + if (_config.homingSwitch == RGHT_SW) { + speedInternal = motor_velocityMMToInternal(_icID, leaveSpeed); + } else { + speedInternal = -1 * motor_velocityMMToInternal(_icID, leaveSpeed); + } + if (_config.invert_direction) speedInternal = -speedInternal; + motor_setVelocityInternal(_icID, speedInternal); + } + } +} + +bool FilterWheel::handleSetLimits(const String& command) { + return true; +} diff --git a/firmware/octoaxes/filterwheel.h b/firmware/octoaxes/filterwheel.h new file mode 100644 index 000000000..fb532b043 --- /dev/null +++ b/firmware/octoaxes/filterwheel.h @@ -0,0 +1,45 @@ +#ifndef FILTER_WHEEL_H +#define FILTER_WHEEL_H + +#include "axis.h" + +class FilterWheel : public Axis { +public: + // Constructor + FilterWheel(uint8_t csPin, uint8_t axisIndex, const char* axisName, uint8_t filterCount = 8); + + // Override the base-class init function to add filter-wheel-specific configuration + bool begin(const AxisConfig& config) override; + + // Filter-wheel-specific features + bool moveToFilter(uint8_t filterPosition); + uint8_t getCurrentFilter() const; + uint8_t getFilterCount() const; + + // Override the state-machine update to add filter-wheel-specific logic + void update() override; + + // Override command processing to add filter-wheel-specific commands + bool processCommand(const String& command) override; + + // Set the filter-wheel position mapping + void setFilterPositions(const float* positions, uint8_t count); + +private: + void performHomingSequence() override; + void performLeavingHome() override; + + uint8_t _filterCount; + uint8_t _currentFilter; + float* _filterPositions; // position (mm) of each filter + bool _slowApproach; // two-phase homing flag: false=fast search for the sensing zone, true=slow precise approach + + // Filter-wheel-specific methods + bool handleSetLimits(const String& command) override; + + bool handleMoveToFilter(const String& command); + float getFilterPosition(uint8_t filterIndex) const; + bool isValidFilterPosition(uint8_t filterPosition) const; +}; + +#endif diff --git a/firmware/octoaxes/illumination.cpp b/firmware/octoaxes/illumination.cpp new file mode 100644 index 000000000..54d91adef --- /dev/null +++ b/firmware/octoaxes/illumination.cpp @@ -0,0 +1,539 @@ +#include "illumination.h" +#include "build_opt.h" +#include +#include + +// ============================================================================= +// State-variable definitions +// ============================================================================= + +int illumination_source = 0; +uint16_t illumination_intensity = 0; +float illumination_intensity_factor = IlluminationConfig::DEFAULT_INTENSITY_FACTOR; +uint8_t led_matrix_r = 0; +uint8_t led_matrix_g = 0; +uint8_t led_matrix_b = 0; +bool illumination_is_on = false; +bool illumination_port_is_on[IlluminationConfig::NUM_PORTS] = {false}; +uint16_t illumination_port_intensity[IlluminationConfig::NUM_PORTS] = {0}; + +// LED matrix pixel array (APA102, BGR order) +static CRGB led_matrix[IlluminationConfig::NUM_LEDS]; + +// whether the matrix addLeds has been registered (prevents double registration) +static bool s_matrix_inited = false; + +// ============================================================================= +// Initialization +// ============================================================================= + +void illumination_init_matrix_early() +{ + if (s_matrix_inited) return; + s_matrix_inited = true; + + // FastLED addLeds: APA102 + BGR + 1 MHz SPI (consistent with legacy Squid init.cpp:44) + FastLED.addLeds( + led_matrix, IlluminationConfig::NUM_LEDS); + + // the APA102 power-on default output is undefined and many batches default to fully lit. Push several all-zero frames + short delays + // to force the LEDs to latch into the off state, countering the power-on transient. + for (int i = 0; i < IlluminationConfig::NUM_LEDS; i++) + led_matrix[i].setRGB(0, 0, 0); + for (int k = 0; k < 4; k++) { + FastLED.show(); + delay(2); + } +} + +void illumination_init() +{ + // safety interlock pin + pinMode(Pins::ILLUMINATION_INTERLOCK, INPUT_PULLUP); + + // TTL port pins: initially LOW (off) + pinMode(Pins::ILLUMINATION_D1, OUTPUT); digitalWrite(Pins::ILLUMINATION_D1, LOW); + pinMode(Pins::ILLUMINATION_D2, OUTPUT); digitalWrite(Pins::ILLUMINATION_D2, LOW); + pinMode(Pins::ILLUMINATION_D3, OUTPUT); digitalWrite(Pins::ILLUMINATION_D3, LOW); + pinMode(Pins::ILLUMINATION_D4, OUTPUT); digitalWrite(Pins::ILLUMINATION_D4, LOW); + pinMode(Pins::ILLUMINATION_D5, OUTPUT); digitalWrite(Pins::ILLUMINATION_D5, LOW); + + // general-purpose digital output pins: behavior consistent with legacy Squid `init_io()` (init.cpp:74). + // includes the autofocus laser AF_LASER (pin 15, legacy Squid `MCU_PINS.AF_LASER`), + // controlled by the host via cmd 41 SET_PIN_LEVEL. Must be explicitly OUTPUT, otherwise while the pin is in + // the INPUT high-impedance state the control board's internal pull-up turns the laser on by default, and digitalWrite in + // INPUT mode does not change the actual level -> cannot turn it off. + static const int kDigitalOutputPins[] = {6, 9, 10, 15}; + for (size_t i = 0; i < sizeof(kDigitalOutputPins)/sizeof(kDigitalOutputPins[0]); i++) { + pinMode(kDigitalOutputPins[i], OUTPUT); + digitalWrite(kDigitalOutputPins[i], LOW); + } + + // LED driver SYNC: 2 MHz PWM, 50% duty cycle + pinMode(Pins::LED_DRIVER_SYNC, OUTPUT); + analogWriteFrequency(Pins::LED_DRIVER_SYNC, 2000000); + analogWrite(Pins::LED_DRIVER_SYNC, 128); + + // LED matrix init (idempotent: skip if the early version was already called earlier in setup) + illumination_init_matrix_early(); + + // DAC init + set_DAC8050x_config(); + set_DAC8050x_default_gain(); + + // state-variable init + illumination_intensity_factor = IlluminationConfig::DEFAULT_INTENSITY_FACTOR; + illumination_is_on = false; + for (int i = 0; i < IlluminationConfig::NUM_PORTS; i++) { + illumination_port_is_on[i] = false; + illumination_port_intensity[i] = 0; + } + + DEBUG_PRINTLN("Illumination initialized"); +} + +// ============================================================================= +// Safety interlock +// ============================================================================= + +bool illumination_interlock_ok() +{ +#ifdef DISABLE_LASER_INTERLOCK + return true; +#else + return digitalRead(Pins::ILLUMINATION_INTERLOCK) == LOW; +#endif +} + +// ============================================================================= +// DAC80508 driver +// ============================================================================= + +void set_DAC8050x_gain(uint8_t div, uint8_t gains) +{ + uint16_t value = (uint16_t(div) << 8) | gains; + SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE2)); + digitalWrite(Pins::DAC8050x_CS, LOW); + SPI.transfer(IlluminationConfig::DAC_GAIN_ADDR); + SPI.transfer16(value); + digitalWrite(Pins::DAC8050x_CS, HIGH); + SPI.endTransaction(); +} + +void set_DAC8050x_default_gain() +{ + set_DAC8050x_gain(IlluminationConfig::DAC_DEFAULT_DIV, + IlluminationConfig::DAC_DEFAULT_GAINS); +} + +void set_DAC8050x_config() +{ + uint16_t value = 0; + SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE2)); + digitalWrite(Pins::DAC8050x_CS, LOW); + SPI.transfer(IlluminationConfig::DAC_CONFIG_ADDR); + SPI.transfer16(value); + digitalWrite(Pins::DAC8050x_CS, HIGH); + SPI.endTransaction(); +} + +void set_DAC8050x_output(int channel, uint16_t value) +{ + // entry validation: the DAC80508 has only 8 DAC channels (0-7). channel is used as a register address offset + // (DAC_DAC_ADDR + channel); out-of-range would write to control registers like CONFIG/GAIN and could lock up the device. + if (channel < 0 || channel > 7) + return; + SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE2)); + digitalWrite(Pins::DAC8050x_CS, LOW); + SPI.transfer(IlluminationConfig::DAC_DAC_ADDR + channel); + SPI.transfer16(value); + digitalWrite(Pins::DAC8050x_CS, HIGH); + SPI.endTransaction(); +} + +// ============================================================================= +// LED matrix helper functions (internal use) +// ============================================================================= + +static void led_set_all(uint8_t r, uint8_t g, uint8_t b) +{ + for (int i = 0; i < IlluminationConfig::NUM_LEDS; i++) + led_matrix[i].setRGB(r, g, b); +} + +static void led_set_left(uint8_t r, uint8_t g, uint8_t b) +{ + for (int i = 0; i < IlluminationConfig::NUM_LEDS / 2; i++) + led_matrix[i].setRGB(r, g, b); +} + +static void led_set_right(uint8_t r, uint8_t g, uint8_t b) +{ + for (int i = IlluminationConfig::NUM_LEDS / 2; i < IlluminationConfig::NUM_LEDS; i++) + led_matrix[i].setRGB(r, g, b); +} + +static void led_set_top(uint8_t r, uint8_t g, uint8_t b) +{ + static const int idx[] = { + 0, 1, 2, 3, + 15, 14, 13, 12, + 16, 17, 18, 19, 20, 21, + 39, 38, 37, 36, 35, 34, + 40, 41, 42, 43, 44, 45, + 63, 62, 61, 60, 59, 58, + 64, 65, 66, 67, 68, 69, + 87, 86, 85, 84, 83, 82, + 88, 89, 90, 91, 92, 93, + 111, 110, 109, 108, 107, 106, + 112, 113, 114, 115, + 127, 126, 125, 124}; + for (int i = 0; i < 64; i++) + led_matrix[idx[i]].setRGB(r, g, b); +} + +static void led_set_bottom(uint8_t r, uint8_t g, uint8_t b) +{ + static const int idx[] = { + 4, 5, 6, 7, + 11, 10, 9, 8, + 22, 23, 24, 25, 26, 27, + 33, 32, 31, 30, 29, 28, + 46, 47, 48, 49, 50, 51, + 57, 56, 55, 54, 53, 52, + 70, 71, 72, 73, 74, 75, + 81, 80, 79, 78, 77, 76, + 94, 95, 96, 97, 98, 99, + 105, 104, 103, 102, 101, 100, + 116, 117, 118, 119, + 123, 122, 121, 120}; + for (int i = 0; i < 64; i++) + led_matrix[idx[i]].setRGB(r, g, b); +} + +static void led_set_low_na(uint8_t r, uint8_t g, uint8_t b) +{ + led_matrix[45].setRGB(r, g, b); led_matrix[46].setRGB(r, g, b); + led_matrix[56].setRGB(r, g, b); led_matrix[57].setRGB(r, g, b); + led_matrix[58].setRGB(r, g, b); led_matrix[59].setRGB(r, g, b); + led_matrix[68].setRGB(r, g, b); led_matrix[69].setRGB(r, g, b); + led_matrix[70].setRGB(r, g, b); led_matrix[71].setRGB(r, g, b); + led_matrix[81].setRGB(r, g, b); led_matrix[82].setRGB(r, g, b); +} + +static void led_set_left_dot(uint8_t r, uint8_t g, uint8_t b) +{ + led_matrix[3].setRGB(r, g, b); led_matrix[4].setRGB(r, g, b); + led_matrix[11].setRGB(r, g, b); led_matrix[12].setRGB(r, g, b); +} + +static void led_set_right_dot(uint8_t r, uint8_t g, uint8_t b) +{ + led_matrix[115].setRGB(r, g, b); led_matrix[116].setRGB(r, g, b); + led_matrix[123].setRGB(r, g, b); led_matrix[124].setRGB(r, g, b); +} + +// ============================================================================= +// LED matrix public functions +// ============================================================================= + +void clear_matrix() +{ + for (int i = 0; i < IlluminationConfig::NUM_LEDS; i++) + led_matrix[i].setRGB(0, 0, 0); + FastLED.show(); +} + +// LED matrix R/G channel byte mapping: +// default (no LED_MATRIX_SWAP_RG macro): call led_set_* in literal order (r, g), +// which, with the FastLED BGR template + standard APA102 LEDs (byte order B/G/R), gives correct colors. +// with -D LED_MATRIX_SWAP_RG defined: swap the r/g arguments, for compatibility with the old hardware batch (byte order +// B/R/G). Equivalent to the pre-2026-05-15 historical behavior, consistent with legacy Squid functions.cpp. +// +// history: in the legacy Squid + old-hardware-LED era the code used a (g, r) swap to compensate for the hardware BRG order; +// after the new LED batch reverted to standard BGR, the swap instead made the user's R/G input display reversed. See SESSION.md. +#ifdef LED_MATRIX_SWAP_RG + #define LED_RG_ARGS(r_val, g_val) (g_val), (r_val) +#else + #define LED_RG_ARGS(r_val, g_val) (r_val), (g_val) +#endif + +void turn_on_LED_matrix_pattern(int pattern, uint8_t r, uint8_t g, uint8_t b) +{ + // intensity scaling (0-255 -> 0-LED_MAX_INTENSITY), note: APA102 BGR order + uint8_t scaled_g = uint8_t(float(g) / 255.0f * IlluminationConfig::LED_MAX_INTENSITY * IlluminationConfig::GREEN_ADJUSTMENT); + uint8_t scaled_r = uint8_t(float(r) / 255.0f * IlluminationConfig::LED_MAX_INTENSITY * IlluminationConfig::RED_ADJUSTMENT); + uint8_t scaled_b = uint8_t(float(b) / 255.0f * IlluminationConfig::LED_MAX_INTENSITY * IlluminationConfig::BLUE_ADJUSTMENT); + + led_set_all(0, 0, 0); // clear first + + switch (pattern) + { + case IlluminationConfig::LED_ARRAY_FULL: + led_set_all(LED_RG_ARGS(scaled_r, scaled_g), scaled_b); break; + case IlluminationConfig::LED_ARRAY_LEFT_HALF: + led_set_left(LED_RG_ARGS(scaled_r, scaled_g), scaled_b); break; + case IlluminationConfig::LED_ARRAY_RIGHT_HALF: + led_set_right(LED_RG_ARGS(scaled_r, scaled_g), scaled_b); break; + case IlluminationConfig::LED_ARRAY_LEFTB_RIGHTR: + led_set_left(0, 0, scaled_b); + led_set_right(LED_RG_ARGS(scaled_r, 0), 0); + break; + case IlluminationConfig::LED_ARRAY_LOW_NA: + led_set_low_na(LED_RG_ARGS(scaled_r, scaled_g), scaled_b); break; + case IlluminationConfig::LED_ARRAY_LEFT_DOT: + led_set_left_dot(LED_RG_ARGS(scaled_r, scaled_g), scaled_b); break; + case IlluminationConfig::LED_ARRAY_RIGHT_DOT: + led_set_right_dot(LED_RG_ARGS(scaled_r, scaled_g), scaled_b); break; + case IlluminationConfig::LED_ARRAY_TOP_HALF: + led_set_top(LED_RG_ARGS(scaled_r, scaled_g), scaled_b); break; + case IlluminationConfig::LED_ARRAY_BOTTOM_HALF: + led_set_bottom(LED_RG_ARGS(scaled_r, scaled_g), scaled_b); break; + default: break; + } + FastLED.show(); +} + +// ============================================================================= +// Port-mapping helpers +// ============================================================================= + +int illumination_source_to_port_index(int source) +{ + switch (source) + { + case IlluminationConfig::D1: return 0; // 11 → 0 + case IlluminationConfig::D2: return 1; // 12 → 1 + case IlluminationConfig::D3: return 2; // 14 -> 2 (out of order!) + case IlluminationConfig::D4: return 3; // 13 -> 3 (out of order!) + case IlluminationConfig::D5: return 4; // 15 → 4 + default: return -1; + } +} + +int port_index_to_pin(int port_index) +{ + switch (port_index) + { + case 0: return Pins::ILLUMINATION_D1; + case 1: return Pins::ILLUMINATION_D2; + case 2: return Pins::ILLUMINATION_D3; + case 3: return Pins::ILLUMINATION_D4; + case 4: return Pins::ILLUMINATION_D5; + default: return -1; + } +} + +int port_index_to_dac_channel(int port_index) +{ + if (port_index >= 0 && port_index < 5) + return port_index; + return -1; +} + +// ============================================================================= +// Legacy illumination API +// ============================================================================= + +void turn_on_illumination() +{ + illumination_is_on = true; + + // sync the multi-port state (backward compatible) + int port_index = illumination_source_to_port_index(illumination_source); + if (port_index >= 0) + illumination_port_is_on[port_index] = true; + + switch (illumination_source) + { + case IlluminationConfig::LED_ARRAY_FULL: + case IlluminationConfig::LED_ARRAY_LEFT_HALF: + case IlluminationConfig::LED_ARRAY_RIGHT_HALF: + case IlluminationConfig::LED_ARRAY_LEFTB_RIGHTR: + case IlluminationConfig::LED_ARRAY_LOW_NA: + case IlluminationConfig::LED_ARRAY_LEFT_DOT: + case IlluminationConfig::LED_ARRAY_RIGHT_DOT: + case IlluminationConfig::LED_ARRAY_TOP_HALF: + case IlluminationConfig::LED_ARRAY_BOTTOM_HALF: + turn_on_LED_matrix_pattern(illumination_source, + led_matrix_r, led_matrix_g, led_matrix_b); + break; + case IlluminationConfig::LED_EXTERNAL_FET: + break; + case IlluminationConfig::D1: + if (illumination_interlock_ok()) + digitalWrite(Pins::ILLUMINATION_D1, HIGH); + break; + case IlluminationConfig::D2: + if (illumination_interlock_ok()) + digitalWrite(Pins::ILLUMINATION_D2, HIGH); + break; + case IlluminationConfig::D3: + if (illumination_interlock_ok()) + digitalWrite(Pins::ILLUMINATION_D3, HIGH); + break; + case IlluminationConfig::D4: + if (illumination_interlock_ok()) + digitalWrite(Pins::ILLUMINATION_D4, HIGH); + break; + case IlluminationConfig::D5: + if (illumination_interlock_ok()) + digitalWrite(Pins::ILLUMINATION_D5, HIGH); + break; + default: break; + } +} + +void turn_off_illumination() +{ + // sync the multi-port state (backward compatible) + int port_index = illumination_source_to_port_index(illumination_source); + if (port_index >= 0) + illumination_port_is_on[port_index] = false; + + switch (illumination_source) + { + case IlluminationConfig::LED_ARRAY_FULL: + case IlluminationConfig::LED_ARRAY_LEFT_HALF: + case IlluminationConfig::LED_ARRAY_RIGHT_HALF: + case IlluminationConfig::LED_ARRAY_LEFTB_RIGHTR: + case IlluminationConfig::LED_ARRAY_LOW_NA: + case IlluminationConfig::LED_ARRAY_LEFT_DOT: + case IlluminationConfig::LED_ARRAY_RIGHT_DOT: + case IlluminationConfig::LED_ARRAY_TOP_HALF: + case IlluminationConfig::LED_ARRAY_BOTTOM_HALF: + clear_matrix(); + break; + case IlluminationConfig::LED_EXTERNAL_FET: + break; + case IlluminationConfig::D1: digitalWrite(Pins::ILLUMINATION_D1, LOW); break; + case IlluminationConfig::D2: digitalWrite(Pins::ILLUMINATION_D2, LOW); break; + case IlluminationConfig::D3: digitalWrite(Pins::ILLUMINATION_D3, LOW); break; + case IlluminationConfig::D4: digitalWrite(Pins::ILLUMINATION_D4, LOW); break; + case IlluminationConfig::D5: digitalWrite(Pins::ILLUMINATION_D5, LOW); break; + default: break; + } + illumination_is_on = false; +} + +void set_illumination(int source, uint16_t intensity) +{ + illumination_source = source; + illumination_intensity = uint16_t(intensity * illumination_intensity_factor); + + // sync the multi-port intensity (backward compatible) + int port_index = illumination_source_to_port_index(source); + if (port_index >= 0) + illumination_port_intensity[port_index] = intensity; + + // write the DAC + switch (source) + { + case IlluminationConfig::D1: set_DAC8050x_output(0, illumination_intensity); break; + case IlluminationConfig::D2: set_DAC8050x_output(1, illumination_intensity); break; + case IlluminationConfig::D3: set_DAC8050x_output(2, illumination_intensity); break; + case IlluminationConfig::D4: set_DAC8050x_output(3, illumination_intensity); break; + case IlluminationConfig::D5: set_DAC8050x_output(4, illumination_intensity); break; + default: break; + } + + // if the light is already on, update the output immediately + if (illumination_is_on) + turn_on_illumination(); +} + +void set_illumination_led_matrix(int source, uint8_t r, uint8_t g, uint8_t b) +{ + // consistent with legacy Squid functions.cpp:359-368: only cache the parameters, do not light immediately, do not touch + // illumination_is_on. The host often uses this command at startup to "preset" the brightfield color/pattern, + // lighting immediately would leave the matrix lit when later switching to a D channel (both on). + illumination_source = source; + led_matrix_r = r; + led_matrix_g = g; + led_matrix_b = b; + if (illumination_is_on) + turn_on_illumination(); // only flush the content to the current source when the light is currently on +} + +// ============================================================================= +// New multi-port API +// ============================================================================= + +void turn_on_port(int port_index) +{ + if (port_index < 0 || port_index >= IlluminationConfig::NUM_PORTS) + return; + int pin = port_index_to_pin(port_index); + if (pin < 0) return; + if (illumination_interlock_ok()) { + digitalWrite(pin, HIGH); + illumination_port_is_on[port_index] = true; + } +} + +void turn_off_port(int port_index) +{ + if (port_index < 0 || port_index >= IlluminationConfig::NUM_PORTS) + return; + int pin = port_index_to_pin(port_index); + if (pin < 0) return; + digitalWrite(pin, LOW); + illumination_port_is_on[port_index] = false; +} + +void set_port_intensity(int port_index, uint16_t intensity) +{ + if (port_index < 0 || port_index >= IlluminationConfig::NUM_PORTS) + return; + int dac_ch = port_index_to_dac_channel(port_index); + if (dac_ch < 0) return; + uint16_t scaled = uint16_t(intensity * illumination_intensity_factor); + set_DAC8050x_output(dac_ch, scaled); + illumination_port_intensity[port_index] = intensity; // store the raw value +} + +void turn_off_all_ports() +{ + for (int i = 0; i < IlluminationConfig::NUM_PORTS; i++) { + int pin = port_index_to_pin(i); + if (pin >= 0) { + digitalWrite(pin, LOW); + illumination_port_is_on[i] = false; + } + } + clear_matrix(); + illumination_is_on = false; +} + +// ============================================================================= +// Serial watchdog +// ============================================================================= + +uint32_t last_serial_message_time = 0; +uint32_t watchdog_timeout_ms = DEFAULT_WATCHDOG_TIMEOUT_MS; +bool watchdog_enabled = false; + +void watchdog_reset_timer() +{ + last_serial_message_time = millis(); +} + +void watchdog_set_timeout(uint32_t timeout_ms) +{ + if (timeout_ms == 0) + timeout_ms = DEFAULT_WATCHDOG_TIMEOUT_MS; + if (timeout_ms > MAX_WATCHDOG_TIMEOUT_MS) + timeout_ms = MAX_WATCHDOG_TIMEOUT_MS; + + watchdog_timeout_ms = timeout_ms; + watchdog_enabled = true; + watchdog_reset_timer(); +} + +void watchdog_check() +{ + if (watchdog_enabled && (millis() - last_serial_message_time >= watchdog_timeout_ms)) { + turn_off_all_ports(); + watchdog_enabled = false; // single-shot, do not repeat + } +} diff --git a/firmware/octoaxes/illumination.h b/firmware/octoaxes/illumination.h new file mode 100644 index 000000000..1d627cc6c --- /dev/null +++ b/firmware/octoaxes/illumination.h @@ -0,0 +1,118 @@ +#ifndef ILLUMINATION_H +#define ILLUMINATION_H + +#include +#include "config.h" + +// ============================================================================= +// Illumination state variables (extern declarations, defined in illumination.cpp) +// ============================================================================= +extern int illumination_source; +extern uint16_t illumination_intensity; +extern float illumination_intensity_factor; +extern uint8_t led_matrix_r; +extern uint8_t led_matrix_g; +extern uint8_t led_matrix_b; +extern bool illumination_is_on; +extern bool illumination_port_is_on[IlluminationConfig::NUM_PORTS]; +extern uint16_t illumination_port_intensity[IlluminationConfig::NUM_PORTS]; + +// ============================================================================= +// Initialization +// ============================================================================= + +// Initialize the illumination hardware: pins, LED matrix, DAC, interlock +void illumination_init(); + +// Only initialize and clear the LED matrix, idempotent. Should be called as early as possible in setup(), +// before time-consuming init such as initializePowerManagement (waiting for the PG signal), +// to extinguish the APA102 power-on default lit state and minimize the user-perceived "startup glow" window. +void illumination_init_matrix_early(); + +// ============================================================================= +// Safety interlock +// ============================================================================= + +// Interlock check: pin 2 LOW means safe +// The compile option -DDISABLE_LASER_INTERLOCK can force a true return (for laser-free systems) +bool illumination_interlock_ok(); + +// ============================================================================= +// DAC80508 driver +// ============================================================================= + +void set_DAC8050x_output(int channel, uint16_t value); +void set_DAC8050x_gain(uint8_t div, uint8_t gains); +void set_DAC8050x_config(); +void set_DAC8050x_default_gain(); + +// ============================================================================= +// LED matrix (APA102, 128 pixels) +// ============================================================================= + +void clear_matrix(); +void turn_on_LED_matrix_pattern(int pattern, uint8_t r, uint8_t g, uint8_t b); + +// ============================================================================= +// Legacy illumination API (single light-source model) +// ============================================================================= + +// turn the light on/off using the current illumination_source +void turn_on_illumination(); +void turn_off_illumination(); + +// set the light-source code and DAC intensity (may update the output immediately) +void set_illumination(int source, uint16_t intensity); + +// set the LED matrix color/pattern (may update the output immediately) +void set_illumination_led_matrix(int source, uint8_t r, uint8_t g, uint8_t b); + +// ============================================================================= +// New multi-port API (v1.0+) +// ============================================================================= + +// turn the GPIO of a given port on/off (interlock check required) +void turn_on_port(int port_index); +void turn_off_port(int port_index); + +// set the DAC intensity of a given port (written after scaling by illumination_intensity_factor) +void set_port_intensity(int port_index, uint16_t intensity); + +// turn off all ports + the LED matrix +void turn_off_all_ports(); + +// ============================================================================= +// Port-mapping helpers +// ============================================================================= + +// legacy light-source code -> port index (11->0, 12->1, 14->2, 13->3, 15->4; others return -1) +int illumination_source_to_port_index(int source); + +// port index -> GPIO pin number (0->5, 1->4, 2->22, 3->3, 4->23; others return -1) +int port_index_to_pin(int port_index); + +// port index -> DAC channel (0-4 mapped directly; others return -1) +int port_index_to_dac_channel(int port_index); + +// ============================================================================= +// Serial watchdog (automatically turns off illumination after a communication loss) +// ============================================================================= + +// Watchdog default/max timeout (ms) +static const uint32_t DEFAULT_WATCHDOG_TIMEOUT_MS = 5000; +static const uint32_t MAX_WATCHDOG_TIMEOUT_MS = 3600000; // 1 hour + +extern uint32_t last_serial_message_time; +extern uint32_t watchdog_timeout_ms; +extern bool watchdog_enabled; + +// reset the watchdog timer (called whenever a valid serial message is received) +void watchdog_reset_timer(); + +// set the watchdog timeout and enable it (timeout_ms=0 uses the default; values above the max are clamped) +void watchdog_set_timeout(uint32_t timeout_ms); + +// called from the main loop: after timeout, turn off all illumination, single-shot +void watchdog_check(); + +#endif // ILLUMINATION_H diff --git a/firmware/octoaxes/joystick.cpp b/firmware/octoaxes/joystick.cpp new file mode 100644 index 000000000..5767efa17 --- /dev/null +++ b/firmware/octoaxes/joystick.cpp @@ -0,0 +1,265 @@ +#include "joystick.h" +#include "axesmrg.h" +#include "build_opt.h" +#include "config.h" +#include "def_octopi_80120.h" +#include "serial.h" +#include "trigger.h" +#include "tmc/motion/MotorControl.h" +#include "tmc/ic/TMC4361A/TMC4361A.h" +#include + +// ============================================================================= +// External variables +// ============================================================================= + +// offset velocity (defined in commandprocessor.cpp) +extern float offset_velocity_x; +extern float offset_velocity_y; + +// ============================================================================= +// Internal constants +// ============================================================================= + +static const unsigned long JOYSTICK_UPDATE_INTERVAL_US = 30000; // 30ms + +// ============================================================================= +// Internal state +// ============================================================================= + +static PacketSerial joystickSerial; + +// cached axis pointers and icID (looked up once at startup) +static Axis *axisX = nullptr; +static Axis *axisY = nullptr; +static Axis *axisZ = nullptr; +static uint8_t icID_X = 0; +static uint8_t icID_Y = 0; +static uint8_t icID_Z = 0; + +// joystick data (written by the PacketSerial callback, read by the main loop) +static volatile int16_t joystick_delta_x = 0; +static volatile int16_t joystick_delta_y = 0; +static volatile bool flag_read_joystick = false; // set true when a new packet arrives, cleared after processing + +// Focus-wheel state +static int32_t focusPosition = 0; +static volatile int32_t focusWheelDelta = 0; // delta accumulated in the callback +static int32_t focusWheelPos = 0; // previous absolute encoder position +static bool firstJoystickPacket = true; // first-packet flag (only records the baseline) +static bool focusPositionSynced = false; // whether focusPosition has been synced with the actual position + +// Periodic timer +static elapsedMicros joystickTimer; + +// protocol-frame statistics counters (read by S:JOYSTICK_STATS) +// byte[9] == 0 -> legacy packet (old joystick, no CRC) +// byte[9] != 0 -> new joystick, verify CRC-8-CCITT(buffer[0..8]), 0x00 mapped to 0x01 +static uint32_t joystick_legacy_count = 0; +static uint32_t joystick_crc_ok_count = 0; +static uint32_t joystick_crc_fail_count = 0; + +// ============================================================================= +// PacketSerial callback: parse the hand controller's 10-byte message +// ============================================================================= + +static void onJoystickPacketReceived(const uint8_t *buffer, size_t size) { + if (size != 10) + return; + + // CRC compatibility gate: byte[9]==0 is treated as legacy (old joystick), non-zero verifies the CRC + uint8_t recv_crc = buffer[9]; + if (recv_crc == 0x00) { + joystick_legacy_count++; + } else { + uint8_t calc = serialProtocol.crc8ccitt(const_cast(buffer), 9); + if (calc == 0x00) calc = 0x01; // consistent with the mapping rule on the joystick side + if (calc != recv_crc) { + joystick_crc_fail_count++; + return; // CRC mismatch, drop the packet + } + joystick_crc_ok_count++; + } + + // bytes[0-3]: focus-wheel absolute encoder position (int32 BE) + int32_t focusWheelNew = (int32_t)((uint32_t)buffer[0] << 24 | + (uint32_t)buffer[1] << 16 | + (uint32_t)buffer[2] << 8 | + (uint32_t)buffer[3]); + if (firstJoystickPacket) { + // the first packet only records the baseline, produces no motion + focusWheelPos = focusWheelNew; + firstJoystickPacket = false; + } else { + int32_t pkt_delta = (focusWheelNew - focusWheelPos) * JOYSTICK_SIGN_Z; + if (pkt_delta != 0) { + DEBUG_PRINT("[FOCUS] pkt_delta="); + DEBUG_PRINT(pkt_delta); + DEBUG_PRINT(" focusWheelNew="); + DEBUG_PRINTLN(focusWheelNew); + } + focusWheelDelta += pkt_delta; + focusWheelPos = focusWheelNew; + } + + // bytes[4-5]: X joystick (int16 BE) + joystick_delta_x = (int16_t)((uint16_t)buffer[4] << 8 | (uint16_t)buffer[5]); + joystick_delta_x *= JOYSTICK_SIGN_X; + + // bytes[6-7]: Y joystick (int16 BE) + joystick_delta_y = (int16_t)((uint16_t)buffer[6] << 8 | (uint16_t)buffer[7]); + joystick_delta_y *= JOYSTICK_SIGN_Y; + + // byte[8]: button + if (buffer[8] != 0) { + joystick_button_pressed = true; + joystick_button_pressed_timestamp = millis(); + } + + flag_read_joystick = true; +} + +// ============================================================================= +// XY-axis joystick velocity control +// ============================================================================= + +static void check_joystick() { + // X axis + if (axisX && !axisX->isMoving() && !axisX->isHomingInProgress()) { + int16_t delta = joystick_delta_x; + if (delta != 0) { + float velocity = offset_velocity_x + + (float(delta) / 32768.0f) * + AxisConstDefinition::MAX_VELOCITY_X_mm; + int32_t velInternal = motor_velocityMMToInternal(icID_X, velocity); + motor_setVelocityInternal(icID_X, velInternal); + } else { + if (enable_offset_velocity) + motor_setVelocityInternal(icID_X, + motor_velocityMMToInternal(icID_X, offset_velocity_x)); + else + motor_stop(icID_X); + } + } + + // Y axis + if (axisY && !axisY->isMoving() && !axisY->isHomingInProgress()) { + int16_t delta = joystick_delta_y; + if (delta != 0) { + float velocity = offset_velocity_y + + (float(delta) / 32768.0f) * + AxisConstDefinition::MAX_VELOCITY_Y_mm; + int32_t velInternal = motor_velocityMMToInternal(icID_Y, velocity); + motor_setVelocityInternal(icID_Y, velInternal); + } else { + if (enable_offset_velocity) + motor_setVelocityInternal(icID_Y, + motor_velocityMMToInternal(icID_Y, offset_velocity_y)); + else + motor_stop(icID_Y); + } + } +} + +// ============================================================================= +// Z-axis focus-wheel control +// ============================================================================= + +static void do_focus_control() { + if (!axisZ || axisZ->isHomingInProgress()) + return; + + // read and zero the accumulated delta + noInterrupts(); + int32_t delta = focusWheelDelta; + focusWheelDelta = 0; + interrupts(); + + if (delta == 0) + return; + + // on first use, sync from the actual position to avoid a stale position at init (inconsistent before/after homing) + if (!focusPositionSynced) { + focusPosition = motor_getPositionMicrosteps(icID_Z); + focusPositionSynced = true; + } + + focusPosition += delta; + + // soft-limit clamp: only effective when soft limits are enabled (valid values exist only after the host's SET_LIMITS) + if (axisZ->isSoftLimitsEnabled()) { + int32_t lowerLimit = (int32_t)tmc4361A_readRegister(icID_Z, TMC4361A_VIRT_STOP_LEFT); + int32_t upperLimit = (int32_t)tmc4361A_readRegister(icID_Z, TMC4361A_VIRT_STOP_RIGHT); + if (focusPosition < lowerLimit) + focusPosition = lowerLimit; + if (focusPosition > upperLimit) + focusPosition = upperLimit; + } + + [[maybe_unused]] int32_t xactual_before = motor_getPositionMicrosteps(icID_Z); + DEBUG_PRINT("[FOCUS] do_focus delta="); + DEBUG_PRINT(delta); + DEBUG_PRINT(" target="); + DEBUG_PRINT(focusPosition); + DEBUG_PRINT(" xactual_before="); + DEBUG_PRINTLN(xactual_before); + + motor_moveToMicrosteps(icID_Z, focusPosition); +} + +// ============================================================================= +// Public API +// ============================================================================= + +void joystick_init() { + // initialize Serial5 @ 115200bps + Serial5.begin(115200); + joystickSerial.setStream(&Serial5); + joystickSerial.setPacketHandler(&onJoystickPacketReceived); + + // cache the axis pointers and icID + axisX = axisManager.findAxisByName("X"); + axisY = axisManager.findAxisByName("Y"); + axisZ = axisManager.findAxisByName("Z"); + + if (axisX) icID_X = axisX->getIcID(); + if (axisY) icID_Y = axisY->getIcID(); + if (axisZ) { + icID_Z = axisZ->getIcID(); + // initialize the focus position to the Z axis's current position + focusPosition = motor_getPositionMicrosteps(icID_Z); + } + + joystickTimer = 0; + + DEBUG_PRINTLN("Joystick system initialized"); +} + +void joystick_update() { + // receive PacketSerial data + joystickSerial.update(); + + // XY joystick: only process when a new packet arrives (consistent with Squid flag_read_joystick) + if (flag_read_joystick) { + if (joystickTimer >= JOYSTICK_UPDATE_INTERVAL_US) { + joystickTimer -= JOYSTICK_UPDATE_INTERVAL_US; + check_joystick(); + } + flag_read_joystick = false; + } + + // Z focus wheel: run unconditionally every loop (consistent with Squid, outside flag_read_joystick) + do_focus_control(); +} + +void joystick_print_stats() { + // send directly via SerialUSB.println (not DEBUG_PRINTLN) to ensure that even in the production env (teensy41) + // the counters can still be queried; matches the same pattern as S:HWINFO / S:VERSION + char buf[96]; + snprintf(buf, sizeof(buf), + "JOYSTICK_STATS legacy=%lu crc_ok=%lu crc_fail=%lu", + (unsigned long)joystick_legacy_count, + (unsigned long)joystick_crc_ok_count, + (unsigned long)joystick_crc_fail_count); + SerialUSB.println(buf); +} diff --git a/firmware/octoaxes/joystick.h b/firmware/octoaxes/joystick.h new file mode 100644 index 000000000..feea906d2 --- /dev/null +++ b/firmware/octoaxes/joystick.h @@ -0,0 +1,15 @@ +#ifndef JOYSTICK_H +#define JOYSTICK_H + +#include + +// Initialize the hand controller (Serial5 + PacketSerial, called in setup) +void joystick_init(); + +// called from the main loop (PacketSerial receive + 30ms periodic motion update + focus-wheel control) +void joystick_update(); + +// print protocol-frame statistics counters (legacy/crc_ok/crc_fail), used by S:JOYSTICK_STATS +void joystick_print_stats(); + +#endif // JOYSTICK_H diff --git a/firmware/octoaxes/objectives.cpp b/firmware/octoaxes/objectives.cpp new file mode 100644 index 000000000..811752b75 --- /dev/null +++ b/firmware/octoaxes/objectives.cpp @@ -0,0 +1,200 @@ +#include "objectives.h" +#include "build_opt.h" + +Objectives::Objectives(uint8_t csPin, uint8_t axisIndex, const char* axisName, uint8_t objectivesCount) + : Axis(csPin, axisIndex, axisName), _objectivesCount(objectivesCount), _currentObjective(0) { + _objectivePositions = new float[objectivesCount]; + + // Initialize default positions: evenly spaced, assuming each objective is 90 degrees apart + for (uint8_t i = 0; i < objectivesCount; i++) { + _objectivePositions[i] = i * (360.0f / objectivesCount); // in degrees; must be converted to mm when actually used + } + +} + +bool Objectives::begin(const AxisConfig& config) { + // call the base-class init + bool result = Axis::begin(config); + + if (result) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Objectives with "); + DEBUG_PRINT(_objectivesCount); + DEBUG_PRINTLN(" Objectives initialized successfully"); + } + + return result; +} + +void Objectives::update() { + // call the base-class update first + Axis::update(); + +} + +bool Objectives::processCommand(const String& command) { + if (command.startsWith("MOVE_TO_OBJECTIVE")) { + return handleMoveToObjective(command); + } else if (command.startsWith("GET_CURRENT_OBJECTIVE")) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":CURRENT_OBJECTIVE:"); + DEBUG_PRINTLN(_currentObjective); + return true; + } else if (command.startsWith("GET_OBJECTIVE_COUNT")) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":OBJECTIVE_COUNT:"); + DEBUG_PRINTLN(_objectivesCount); + return true; + } else { + // hand other commands to the base class + return Axis::processCommand(command); + } +} + +void Objectives::performHomingSequence() { + if (checkTimeout(_homing_timeout_ms)) { + restoreNormalMicrosteps(); + handleError("Homing timeout"); + return; + } + + uint8_t limit_state = readLimitSwitches(); + + switch (_currentState) { + case STATE_HOMING_INIT: + // directly disable the virtual limits in hardware without changing the _softLimitsEnabled flag + motor_enableSoftLimits(_icID, false, false); + switchToHomingMicrosteps(); + + if (limit_state == _config.homingSwitch) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Already at home position, moving away first..."); + setState(STATE_LEAVING_HOME); + } else { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Starting homing process..."); + + DEBUG_PRINTLN(_config.homingVelocityMM); + int32_t speedInternal = motor_velocityMMToInternal(_icID, _config.homingVelocityMM); + motor_setVelocityInternal(_icID, speedInternal); + setState(STATE_HOMING_SEARCH); + } + break; + + case STATE_HOMING_SEARCH: + if (limit_state == _config.homingSwitch) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Home limit switch triggered!"); + + motor_setCurrentPositionMicrosteps(_icID, 0); + + _checkHomeReachTimeout = 0; + + setState(STATE_HOMING_SET_ZERO); + } + break; + + case STATE_HOMING_SET_ZERO: + // wait for the move to the safe position to complete + if (isMovementComplete() || _checkHomeReachTimeout >= 500 * 1000) { + // restore normal microstepping + restoreNormalMicrosteps(); + // set the current position to 0 + DEBUG_PRINT(_axisName); + + if (_checkHomeReachTimeout > 500 * 1000) { + DEBUG_PRINTLN(":Homing Set Current Position to 0 position Timeout"); + } + + DEBUG_PRINTLN(":Homing completed! Current position set to 0"); + + // after homing completes, restore soft limits and PID + if (_softLimitsEnabled) { + enableSoftLimits(true); + } + if (_pidState.enabled) { + motor_enablePID(_icID); + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":PID re-enabled after homing"); + } + + setState(STATE_IDLE); + } else { + // optional: add a progress display + static unsigned long lastProgressTime = 0; + if (millis() - lastProgressTime > 500) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Moving to safe position... Current :"); + DEBUG_PRINT(getCurrentPositionMicrosteps()); + DEBUG_PRINT(" microsteps, Target: "); + DEBUG_PRINT(motor_getTargetMicrosteps(_icID)); + DEBUG_PRINTLN(" microsteps"); + lastProgressTime = millis(); + } + } + break; + + default: + break; + } +} + +void Objectives::performLeavingHome() { + if (checkTimeout(LEAVING_HOME_TIMEOUT_MS)) { + handleError("Leaving home timeout"); + return; + } + + uint8_t limit_state = readLimitSwitches(); + + if (_currentState == STATE_LEAVING_HOME) { + if (!(limit_state == _config.homingSwitch)) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Left home position, starting homing..."); + + // start the actual homing search + int32_t speedInternal = motor_velocityMMToInternal(_icID, _config.homingVelocityMM); + motor_setVelocityInternal(_icID, speedInternal); + setState(STATE_HOMING_SEARCH); + } else { + // keep moving to leave the home position + // set the correct leaving direction based on the limit-switch type + int32_t speedInternal; + if (_config.homingSwitch == RGHT_SW) { + speedInternal = motor_velocityMMToInternal(_icID, _config.homingVelocityMM); // move left to leave the right limit + } else { + speedInternal = -1 * motor_velocityMMToInternal(_icID, _config.homingVelocityMM); // move right to leave the left limit + } + motor_setVelocityInternal(_icID, speedInternal); + } + } +} + +bool Objectives::handleSetLimits(const String& command) { + return true; +} + +bool Objectives::handleMoveToObjective(const String& command) { + int space1 = command.indexOf(' '); + if (space1 == -1) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":MOVE_TO_OBJECTIVE ERROR: Invalid format"); + return false; + } + + String filterStr = command.substring(space1 + 1); + [[maybe_unused]] uint8_t ObjectivePosition = (uint8_t)filterStr.toInt(); + + /* + if (!moveToFilter(ObjectivePosition)) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":MOVE_TO_OBJECTIVE ERROR: Movement failed"); + return false; + } + */ + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":MOVE_TO_OBJECTIVE: Moving to filter "); + DEBUG_PRINTLN(ObjectivePosition); + return true; +} diff --git a/firmware/octoaxes/objectives.h b/firmware/octoaxes/objectives.h new file mode 100644 index 000000000..0058df340 --- /dev/null +++ b/firmware/octoaxes/objectives.h @@ -0,0 +1,32 @@ +#ifndef OBJECTIVES_H +#define OBJECTIVES_H + +#include "axis.h" + +class Objectives : public Axis { +public: + // Constructor + Objectives(uint8_t csPin, uint8_t axisIndex, const char* axisName, uint8_t objectivesCount = 4); + + // Override the base-class init function to add filter-wheel-specific configuration + bool begin(const AxisConfig& config) override; + + // Override the state-machine update to add filter-wheel-specific logic + void update() override; + + // Override command processing to add filter-wheel-specific commands + bool processCommand(const String& command) override; + +private: + void performHomingSequence() override; + void performLeavingHome() override; + + uint8_t _objectivesCount; + uint8_t _currentObjective; + float* _objectivePositions; + + bool handleMoveToObjective(const String& command); + bool handleSetLimits(const String& command) override; +}; + +#endif diff --git a/firmware/octoaxes/octoaxes.ino b/firmware/octoaxes/octoaxes.ino new file mode 100644 index 000000000..4854795fc --- /dev/null +++ b/firmware/octoaxes/octoaxes.ino @@ -0,0 +1,199 @@ +#include "axesmrg.h" +#include "build_opt.h" +#include "filterwheel.h" +#include "illumination.h" +#include "joystick.h" +#include "trigger.h" +#include "objectives.h" +#include "serial.h" +#include "stepaxis.h" +#include "tmc/hal/TMC_SPI.h" +#include "tmc/motion/MotorControl.h" +#include "tmc/ic/TMC4361A/TMC4361A.h" +#include "utils.h" + +void initializeClock(uint8_t clk_pin, uint32_t frequence) { + pinMode(clk_pin, OUTPUT); + analogWriteFrequency(clk_pin, frequence); + analogWrite(clk_pin, 128); +} + +void initializeSPIAndPins() { + // Disable all axes + for (uint8_t i = 0; i < sizeof(Pins::CONTROL_PINS); i++) { + pinMode(Pins::CONTROL_PINS[i], OUTPUT); + digitalWrite(Pins::CONTROL_PINS[i], HIGH); + } + + for (uint8_t i = 0; i < sizeof(Pins::STANDARD_CONTROL_PINS); i++) { + pinMode(Pins::STANDARD_CONTROL_PINS[i], OUTPUT); + digitalWrite(Pins::STANDARD_CONTROL_PINS[i], HIGH); + } + + // Initialize SPI + SPI.begin(); + delay(50); // 50ms delay, using explicit time units +} + +bool initializePowerManagement() { + pinMode(Pins::POWER_GOOD, INPUT_PULLUP); + + // Disable the DAC pins + pinMode(Pins::DAC8050x_CS, OUTPUT); + digitalWrite(Pins::DAC8050x_CS, HIGH); + + delay(100); + + // Wait for power to be ready + unsigned long startTime = millis(); + while (!digitalRead(Pins::POWER_GOOD)) { + if (millis() - startTime > 5000) { // 5-second timeout + DEBUG_PRINTLN("Power management initialization timeout"); + return false; + } + delay(50); + } + + return true; +} + +bool initializeSystem() { + // Initialize power management + if (!initializePowerManagement()) { + return false; + } + + // Initialize the clock + initializeClock(Pins::TMC4361_STANDARD_CLK, + SystemConfig::TMC4361_CLOCK_FREQUENCY); + initializeClock(Pins::TMC4361_EXPAND_CLK, + SystemConfig::TMC4361_CLOCK_FREQUENCY); + + // Initialize SPI and pins + initializeSPIAndPins(); + + // Initialize the illumination system (pins, LED matrix, DAC, interlock) + illumination_init(); + + // Initialize the trigger system (pins, strobe timer) + trigger_init(); + + // Initialize the new-architecture motion-control subsystem + motor_initSubsystem(); + + // Create axis objects and add them to the manager + // + // Important (2026-05-08 fix): the axisName <-> CS pin mapping is aligned with the legacy Squid hardware wiring + // + // legacy Squid firmware internal axis index vs protocol axis number mapping (def_v1.h:11-21): + // Protocol: AXIS_X=0, AXIS_Y=1 + // Internal: x=1, y=0 (the comment explicitly says "Internal indices match hardware wiring") + // -> legacy Squid hardware actual wiring: + // pin_TMC4361_CS[0]=41 -> physical Y motor (because internal y=0) + // pin_TMC4361_CS[1]=36 -> physical X motor (because internal x=1) + // + // Therefore axisName="X" must be bound to CS=36 (Pins::Y_AXIS_CS) to correctly drive the physical X motor. + // Previously axisName="X" + Pins::X_AXIS_CS=41 -> actually drove the physical Y motor, + // causing the legacy Squid jog-X freeze (X moving to 79.9mm actually triggered the physical Y limit). + // + // axisIndex (icID) is just the internal array index and does not affect the physical CS mapping. + Axis *xAxis = new StepAxis (Pins::Y_AXIS_CS, 0, "X"); // CS=36 = physical X motor + Axis *yAxis = new StepAxis (Pins::X_AXIS_CS, 1, "Y"); // CS=41 = physical Y motor + Axis *zAxis = new StepAxis (Pins::Z_AXIS_CS, 2, "Z"); + Axis *wAxis = new FilterWheel(Pins::W_AXIS_CS, 3, "W"); + // W2 = the second filter wheel, taking over the original E4 hardware (CS=pin 16, CLK=pin 28 = TMC4361_EXPAND_CLK), + // fully consistent with legacy Squid pin_TMC4361_CS[4]=16 / pin_TMC4361_CLK_W2=28. + // the board may be absent: axesmrg.cpp::beginAll deletes + nullptrs this slot when SPI does not respond, + // so all W2 handlers' if (axis) guards turn commands into a silent no-op without affecting other axes. + Axis *w2Axis = new FilterWheel(Pins::W2_AXIS_CS, 4, "W2"); + // E1 = objective changer (4 objectives), connected to the EXPAND1 hardware (CS=pin 19, CLK=pin 28 = TMC4361_EXPAND_CLK). + // 2026-05-29: on this board the icID=5 slot is the objective changer (the W filter wheel is left unchanged). + // the protocol uses dedicated MOVE_TURRET/MOVETO_TURRET + HOME_OR_ZERO axis=7 (protocolAxisToName case 7 -> "Turret"). + // the board may be absent: axesmrg.cpp::beginAll deletes + nullptrs this slot when SPI does not respond. + Axis *turretAxis = new Objectives (Pins::EXPAND1_AXIS_CS, 5, "Turret", 4); + + // add in axisIndex order: X(0), Y(1), Z(2), W(3), W2(4), E1(5) + if (!axisManager.addAxis(xAxis) || !axisManager.addAxis(yAxis) || + !axisManager.addAxis(zAxis) || !axisManager.addAxis(wAxis) || + !axisManager.addAxis(w2Axis) || !axisManager.addAxis(turretAxis)) { + DEBUG_PRINTLN("Failed to add axes to manager"); + return false; + } + + // Initialize all axes + // Note: beginAll() returning false means **at least one axis failed begin** (typical case: + // TMC4361A SPI not responding, so after motor_initMotionController writes SW_RESET, reading + // VERSION_NO returns 0/-1). **No longer treated as fatal** -- serial communication and debug commands + // (S:VERSION / S:HWINFO / S:DUMPREGS) must remain available, otherwise the SPI failure root cause cannot be diagnosed + // on-site. The failed axis is already identified by axis.cpp's DEBUG_PRINT(_axisName + + // ":BEGIN_FAIL ...") printed to the serial port. + if (!axisManager.beginAll()) { + DEBUG_PRINTLN("WARNING: beginAll() reported partial axis failure (see :BEGIN_FAIL above). Continuing so serial diagnostics remain available."); + } + + // Initialize the hand controller (Serial5 + PacketSerial) + joystick_init(); + + return true; +} + +void setup() { + // Initialize the serial port + serialProtocol.begin(115200, 300); + + // Initialize the status indicator LED + initializeStartupLED(); + + // clear the APA102 matrix as early as possible to minimize the "startup glow" window. + // the subsequent initializePowerManagement (waiting for PG) + delay + clock + SPI init + // may total hundreds of ms to 5s, during which the APA102 stays in its power-on default lit state. + illumination_init_matrix_early(); + + DEBUG_PRINTLN("Initializing system..."); + + // Initialize the system + if (!initializeSystem()) { + DEBUG_PRINTLN("System initialization failed!"); + while (1) { + delay(1000); // halt execution + } + } + + DEBUG_PRINTLN("System initialized successfully"); +} + +void loop() { + static bool firstLoop = true; + if (firstLoop) { + DEBUG_PRINTLN("MAIN_LOOP_ENTERED"); // confirm entry into the main loop + firstLoop = false; + } + + // Safety interlock check: when the interlock opens, directly pull the TTL laser ports low (hardcoded GPIO, zero overhead) + if (!illumination_interlock_ok()) { + digitalWrite(Pins::ILLUMINATION_D1, LOW); + digitalWrite(Pins::ILLUMINATION_D2, LOW); + digitalWrite(Pins::ILLUMINATION_D3, LOW); + digitalWrite(Pins::ILLUMINATION_D4, LOW); + digitalWrite(Pins::ILLUMINATION_D5, LOW); + } + + // Serial watchdog: automatically turn off all illumination after a communication-loss timeout + watchdog_check(); + + // Update trigger-pulse recovery + trigger_update(); + + // Process serial debug commands + serialProtocol.processSerialCommands(); + + // 10ms periodic position reporting (compatible with the legacy Squid protocol) + serialProtocol.send_position_update(); + + // Update the hand controller (PacketSerial receive + joystick/focus-wheel control) + joystick_update(); + + // Update all axis state machines + axisManager.updateAll(); + +} diff --git a/firmware/octoaxes/platformio.ini b/firmware/octoaxes/platformio.ini new file mode 100644 index 000000000..4b7101e0d --- /dev/null +++ b/firmware/octoaxes/platformio.ini @@ -0,0 +1,195 @@ +; ============================================================================= +; Octoaxes Firmware - PlatformIO Configuration +; Multi-axis Motion Controller for SQUID Microscope Platform +; Target: Teensy 4.1 (ARM Cortex-M7 @ 600MHz) +; ============================================================================= + +[platformio] +name = octoaxes +description = Multi-axis motion control firmware for precision microscopy +default_envs = teensy41 +src_dir = . + +; generate compile_commands.json for clangd LSP support +; run pio run -t compiledb to generate it +extra_configs = + +; ============================================================================= +; Common Settings (shared across all environments) +; ============================================================================= +[common] +framework = arduino +platform = teensy +board = teensy41 + +; Library dependencies +lib_deps = + fastled/FastLED @ ^3.6.0 + bakercp/PacketSerial @ ^1.4.0 + +; Common build flags +build_flags = + -D ARDUINO_TEENSY41 + -I tmc + ; Optimization + -O2 + ; Warnings + -Wall + -Wextra + -Wno-unused-parameter + -Wno-deprecated-copy ; suppress the warning from the Teensy framework's DMAChannel.h + +; Source filter +build_src_filter = + +<*> + -<.git/> + - + - + +; ============================================================================= +; Production Environment +; ============================================================================= +[env:teensy41] +extends = common + +; CPU frequency: 600MHz (default), options: 24/150/396/450/528/600/720/816/912/960 +board_build.f_cpu = 600000000L + +; Build configuration +build_type = release +build_flags = + ${common.build_flags} + -D NDEBUG + -ffunction-sections + -fdata-sections + +; Linker optimization - remove unused code +build_unflags = + -Os + +; Upload settings +upload_protocol = teensy-cli + +; Serial monitor +monitor_speed = 2000000 +monitor_echo = yes +monitor_eol = LF +monitor_filters = + default + time + +; ============================================================================= +; Debug Environment +; ============================================================================= +[env:teensy41_debug] +extends = common + +board_build.f_cpu = 600000000L + +build_type = debug +build_flags = + ${common.build_flags} + -D DEBUG + -D CORE_DEBUG_LEVEL=5 + -g3 + -ggdb + +upload_protocol = teensy-cli + +monitor_speed = 2000000 +monitor_echo = yes +monitor_eol = LF +monitor_filters = + default + time + log2file + +; ============================================================================= +; Development Environment (with extra warnings) +; ============================================================================= +[env:teensy41_dev] +extends = common + +board_build.f_cpu = 600000000L + +build_type = debug +build_flags = + ${common.build_flags} + -D DEBUG + -Werror=return-type + -Wshadow + -Wformat=2 + -g + +upload_protocol = teensy-cli + +monitor_speed = 2000000 +monitor_echo = yes +monitor_eol = LF + +; ============================================================================= +; High Performance Environment (overclock to 720MHz) +; WARNING: Ensure adequate cooling before using this configuration +; ============================================================================= +[env:teensy41_fast] +extends = common + +board_build.f_cpu = 720000000L + +build_type = release +build_flags = + ${common.build_flags} + -D NDEBUG + -D HIGH_PERFORMANCE + -O3 + -ffunction-sections + -fdata-sections + +upload_protocol = teensy-cli + +monitor_speed = 2000000 + +; ============================================================================= +; No-Interlock Environment (for stations without laser interlock hardware) +; for stations without a laser interlock signal (pin 2): disables the illumination_interlock_ok check, +; otherwise the D1-D5 TTL outputs (405/488/561/638/730 fluorescence channels) never come up. +; warning: use only when there is confirmed no laser risk. The LED matrix (brightfield) does not go through the interlock and is unaffected. +; ============================================================================= +[env:teensy41_nointerlock] +extends = env:teensy41 + +build_flags = + ${env:teensy41.build_flags} + -D DISABLE_LASER_INTERLOCK + +; ============================================================================= +; Legacy LED Matrix Environment (old hardware LED batch with reversed R/G byte order) +; use this environment when the LED matrix shows "R slider drives green, G slider drives red, B normal". +; root cause: the FastLED template assumes the LED byte order is BGR, but some old APA102 batches are BRG. +; enabling -D LED_MATRIX_SWAP_RG makes the firmware swap the R/G arguments when calling led_set_*, +; equivalent to the historical behavior of legacy Squid functions.cpp:1933. +; see the illumination.cpp::LED_RG_ARGS comment for details. +; ============================================================================= +[env:teensy41_legacyled] +extends = env:teensy41 + +build_flags = + ${env:teensy41.build_flags} + -D LED_MATRIX_SWAP_RG + +[env:teensy41_nointerlock_legacyled] +extends = env:teensy41 + +build_flags = + ${env:teensy41.build_flags} + -D DISABLE_LASER_INTERLOCK + -D LED_MATRIX_SWAP_RG + +; ============================================================================= +; Other Build Options (uncomment as needed) +; ============================================================================= +; To enable verbose SPI debugging: +; Add -D SPI_DEBUG to build_flags +; +; To enable motion profiling output: +; Add -D MOTION_PROFILE_DEBUG to build_flags diff --git a/firmware/octoaxes/serial.cpp b/firmware/octoaxes/serial.cpp new file mode 100644 index 000000000..504eaad26 --- /dev/null +++ b/firmware/octoaxes/serial.cpp @@ -0,0 +1,666 @@ +#include "serial.h" +#include "axesmrg.h" +#include "build_opt.h" +#include "commandprocessor.h" +#include "config.h" +#include "illumination.h" +#include "joystick.h" +#include "trigger.h" +#include "tmc/motion/MotorControl.h" +#include "tmc/ic/TMC4361A/TMC4361A.h" +#include + +// Protocol status bytes +static const uint8_t STATUS_COMPLETED = 0; +static const uint8_t STATUS_IN_PROGRESS = 1; +static const uint8_t STATUS_CRC_ERROR = 2; + +// firmware version (byte[22]: high nibble=major, low nibble=minor) +static const uint8_t FIRMWARE_VERSION_MAJOR = 1; +static const uint8_t FIRMWARE_VERSION_MINOR = 7; + +// position-report period (10ms, consistent with legacy Squid) +static const uint32_t INTERVAL_SEND_POS_US = 10000; + +static const uint8_t CRC_TABLE[256] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, + 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, + 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9, + 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, + 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, + 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE, + 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, + 0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, + 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80, + 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, + 0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, + 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10, + 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, + 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, + 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7, + 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, + 0xFA, 0xFD, 0xF4, 0xF3}; + +SerialProtocolHandler serialProtocol; + +static const uint32_t VERSION = 106; + +SerialProtocolHandler::SerialProtocolHandler() + : buffer_rx_ptr(0), cmd_id(0), mcu_cmd_execution_in_progress(false), + checksum_error(false) { + memset(buffer_rx, 0, sizeof(buffer_rx)); +} + +void SerialProtocolHandler::begin(long baudRate, uint32_t timeout) { + SerialUSB.begin(baudRate); + delay(500); + SerialUSB.setTimeout(timeout); + buffer_rx_ptr = 0; + while (!SerialUSB) { + ; // wait for the serial connection + } +} + + +void SerialProtocolHandler::sendDebugInfo(const char *format, ...) { + char buffer[256]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + + // send debug info with the protocol header + DEBUG_PRINTLN(buffer); +} + +uint8_t SerialProtocolHandler::crc8ccitt(byte *data, uint8_t n) { + uint8_t val = 0; + uint8_t *pos = (uint8_t *)data; + uint8_t *end = pos + n; + + while (pos < end) { + val = CRC_TABLE[val ^ *pos]; + pos++; + } + + return val; +} + +bool SerialProtocolHandler::checkForCommand() { + bool commandReceived = false; + + // read serial data + while (SerialUSB.available()) { + buffer_rx[buffer_rx_ptr] = SerialUSB.read(); + buffer_rx_ptr = buffer_rx_ptr + 1; + + if (buffer_rx_ptr == CMD_LENGTH) { + buffer_rx_ptr = 0; + cmd_id = buffer_rx[0]; + + // checksum check + uint8_t checksum = crc8ccitt(buffer_rx, CMD_LENGTH - 1); + if (checksum != buffer_rx[CMD_LENGTH - 1]) { + checksum_error = true; + // flush the serial buffer, since byte-level desync can also cause this error + while (SerialUSB.available()) { + SerialUSB.read(); + } + return false; + } else { + checksum_error = false; + commandReceived = true; + watchdog_reset_timer(); + } + break; // process only one command at a time + } + } + + return commandReceived; +} + +void SerialProtocolHandler::sendResponse(byte cmd_id, byte status, + int32_t x_pos, int32_t y_pos, + int32_t z_pos, int32_t w_pos, + bool joystick_button_pressed) { + byte buffer_tx[MSG_LENGTH]; + memset(buffer_tx, 0, MSG_LENGTH); + + buffer_tx[0] = cmd_id; + buffer_tx[1] = status; + + // X-axis position (bytes 2-5) + buffer_tx[2] = byte(x_pos >> 24); + buffer_tx[3] = byte((x_pos >> 16) & 0xFF); + buffer_tx[4] = byte((x_pos >> 8) & 0xFF); + buffer_tx[5] = byte(x_pos & 0xFF); + + // Y-axis position (bytes 6-9) + buffer_tx[6] = byte(y_pos >> 24); + buffer_tx[7] = byte((y_pos >> 16) & 0xFF); + buffer_tx[8] = byte((y_pos >> 8) & 0xFF); + buffer_tx[9] = byte(y_pos & 0xFF); + + // Z-axis position (bytes 10-13) + buffer_tx[10] = byte(z_pos >> 24); + buffer_tx[11] = byte((z_pos >> 16) & 0xFF); + buffer_tx[12] = byte((z_pos >> 8) & 0xFF); + buffer_tx[13] = byte(z_pos & 0xFF); + + // W-axis position (bytes 14-17) + buffer_tx[14] = byte(w_pos >> 24); + buffer_tx[15] = byte((w_pos >> 16) & 0xFF); + buffer_tx[16] = byte((w_pos >> 8) & 0xFF); + buffer_tx[17] = byte(w_pos & 0xFF); + + // status byte byte[18]: bit0 = joystick button + static const int BIT_POS_JOYSTICK_BUTTON = 0; + buffer_tx[18] = (joystick_button_pressed ? (1 << BIT_POS_JOYSTICK_BUTTON) : 0); + + // bytes[19-21]: reserved + + // firmware version byte[22]: high nibble=major, low nibble=minor + buffer_tx[22] = (FIRMWARE_VERSION_MAJOR << 4) | (FIRMWARE_VERSION_MINOR & 0x0F); + + // CRC-8-CCITT checksum (computed over byte[0..22]) + uint8_t checksum = crc8ccitt(buffer_tx, MSG_LENGTH - 1); + buffer_tx[MSG_LENGTH - 1] = checksum; + + SerialUSB.write(buffer_tx, MSG_LENGTH); +} + +void SerialProtocolHandler::send_position_update() { +#ifdef DISABLE_BINARY_POS_UPDATE + // a switch temporarily defined in build_opt.h: skip the 24-byte binary position reporting, + // leaving only ASCII debug output on SerialUSB, convenient for the Arduino Serial Monitor + return; +#endif + + // compute any_moving first, used to detect the "movement-complete" falling edge (true->false) + bool any_moving = false; + uint8_t count = axisManager.getAxisCount(); + for (uint8_t i = 0; i < count; i++) { + Axis *axis = axisManager.getAxis(i); + if (axis && (axis->isMoving() || axis->isHomingInProgress())) { + any_moving = true; + break; + } + } + // completion edge: all axes just stopped. Bypass the 10ms heartbeat throttle and immediately send a COMPLETED frame, + // so the host's wait_till_operation_is_completed is woken within < 1ms after the physical stop + // (saves 5ms on average, 10ms heartbeat delay worst case). The falling edge fires only once per transition. + bool falling_edge = _last_any_moving && !any_moving; + _last_any_moving = any_moving; + + if (_us_since_last_pos_update < INTERVAL_SEND_POS_US && !falling_edge) + return; + _us_since_last_pos_update = 0; + + // read each axis position (microsteps, consistent with legacy Squid tmc4361A_currentPosition) + // cache the axis pointers: findAxisByName constructs 4 Strings + 4 equals each time, accumulating per tick + // ~40us * 10000 ticks ~= 400ms wasted. The axis pointers do not change during the axisManager lifetime, + // so static caching is safe (even a first-time nullptr is the real situation, no retry needed) (#4, 2026-05-19) + static Axis *xAxis = nullptr; + static Axis *yAxis = nullptr; + static Axis *zAxis = nullptr; + static Axis *wAxis = nullptr; + static bool axes_cached = false; + if (!axes_cached) { + xAxis = axisManager.findAxisByName("X"); + yAxis = axisManager.findAxisByName("Y"); + zAxis = axisManager.findAxisByName("Z"); + wAxis = axisManager.findAxisByName("W"); + axes_cached = true; + } + + int32_t x_pos = xAxis ? xAxis->getCurrentPositionMicrosteps() : 0; + int32_t y_pos = yAxis ? yAxis->getCurrentPositionMicrosteps() : 0; + int32_t z_pos = zAxis ? zAxis->getCurrentPositionMicrosteps() : 0; + int32_t w_pos = wAxis ? wAxis->getCurrentPositionMicrosteps() : 0; + + // joystick-button fail-safe: auto-clear if not ACKed within 1000ms + if (joystick_button_pressed && + millis() - joystick_button_pressed_timestamp > 1000) { + joystick_button_pressed = false; + } + + uint8_t status; + if (checksum_error) + status = STATUS_CRC_ERROR; + else + status = any_moving ? STATUS_IN_PROGRESS : STATUS_COMPLETED; + + sendResponse(cmd_id, status, x_pos, y_pos, z_pos, w_pos, + joystick_button_pressed); +} + +void SerialProtocolHandler::processSerialCommands() { + static uint32_t lastPrint = 0; + if (millis() - lastPrint > 5000) { // print once every 5 seconds + DEBUG_PRINT("LOOP_ALIVE:"); + DEBUG_PRINTLN(SerialUSB.available()); + lastPrint = millis(); + } + + if (SerialUSB.available() >= 2) { + DEBUG_PRINT("RX_AVAIL:"); + DEBUG_PRINTLN(SerialUSB.available()); // debug: data received + + // peek at the first two bytes without removing them + int firstByte = SerialUSB.peek(); + + if (firstByte == DEBUG_PROTOCOL_HEADER_1) { + // peek at the second byte (the second byte is at index 1) + // we must read the first byte before we can peek at the second byte + SerialUSB.read(); // remove the first byte + int secondByte = SerialUSB.peek(); // peek at the second byte + + if (secondByte == DEBUG_PROTOCOL_HEADER_2) { + // confirmed it is the debug protocol, remove the second byte + SerialUSB.read(); // remove the second byte + processSerialDebugCommands(); + } else { + // not the debug protocol, put the first byte back into the buffer + // since we already removed the first byte, we need to put it back into buffer_rx + buffer_rx[0] = DEBUG_PROTOCOL_HEADER_1; + buffer_rx_ptr = 1; + // continue handling the standard command + // the second byte is still in the serial buffer and will be read in checkForCommand + processSerialStandardCommands(); + } + } else { + // not a debug protocol header, handle the standard command + processSerialStandardCommands(); + } + } else if (SerialUSB.available() == 1) { + // only one byte available, handle the standard command directly + processSerialStandardCommands(); + } +} + +void SerialProtocolHandler::processSerialDebugCommands() { + // read until the newline + String command = SerialUSB.readStringUntil('\n'); + command.trim(); // strip leading and trailing whitespace + + if (command.length() > 0) { + if (command == "S:VERSION") { + // the version reply is always sent (not gated by ENABLE_DEBUG) + char vbuf[32]; + snprintf(vbuf, sizeof(vbuf), "S:VERSION:%lu", (unsigned long)VERSION); + SerialUSB.println(vbuf); + return; + } + + if (command == "S:Engine Start") { + // keep command compatibility; the startup sequence is no longer needed + sendDebugInfo("System already running (Engine Start is no longer required)"); + return; + } + + if (command == "S:ENCPOS") { + char buf[120]; + // first print the W-axis encoder register diagnostics + uint8_t wID = 3; + uint32_t genConf = tmc4361A_readRegister(wID, TMC4361A_GENERAL_CONF); + uint32_t encInConf = tmc4361A_readRegister(wID, TMC4361A_ENC_IN_CONF); + uint32_t stepConf = tmc4361A_readRegister(wID, TMC4361A_STEP_CONF); + uint32_t encInRes = tmc4361A_readRegister(wID, TMC4361A_ENC_IN_RES); + snprintf(buf, sizeof(buf), "S:ENCDIAG:W GENERAL_CONF=0x%08lX diff_dis=%d ser_mode=%d", + (unsigned long)genConf, (int)((genConf >> 12) & 1), (int)((genConf >> 10) & 3)); + SerialUSB.println(buf); + snprintf(buf, sizeof(buf), "S:ENCDIAG:W ENC_IN_CONF=0x%08lX STEP_CONF=0x%08lX ENC_IN_RES=%lu", + (unsigned long)encInConf, (unsigned long)stepConf, (unsigned long)encInRes); + SerialUSB.println(buf); + + // print each axis's encoder position + for (uint8_t i = 0; i < axisManager.getAxisCount(); i++) { + Axis *axis = axisManager.getAxis(i); + if (axis) { + int32_t encPos = (int32_t)tmc4361A_readRegister(i, TMC4361A_ENC_POS); + int32_t xActual = (int32_t)tmc4361A_readRegister(i, TMC4361A_XACTUAL); + snprintf(buf, sizeof(buf), "S:ENCPOS:%s:enc=%ld xactual=%ld dev=%ld", + axis->getAxisName(), (long)encPos, (long)xActual, (long)(encPos - xActual)); + SerialUSB.println(buf); + } + } + SerialUSB.println("S:ENCPOS:END"); + return; + } + + if (command == "S:HWINFO") { + char buf[64]; + for (uint8_t i = 0; i < axisManager.getAxisCount(); i++) { + Axis *axis = axisManager.getAxis(i); + if (axis) { + const char *driverName; + switch (axis->getDriverType()) { + case DRIVER_TMC2660: driverName = "TMC2660"; break; + case DRIVER_TMC2240: driverName = "TMC2240"; break; + default: driverName = "UNKNOWN"; break; + } + snprintf(buf, sizeof(buf), "S:HWINFO:%s:TMC4361A+%s", + axis->getAxisName(), driverName); + SerialUSB.println(buf); + } + } + SerialUSB.println("S:HWINFO:END"); + return; + } + + // S:JOYSTICK_STATS -- print hand-controller protocol-frame statistics + // legacy = byte[9]==0 (old joystick has no CRC) + // crc_ok / crc_fail = new joystick CRC-8-CCITT verification results + if (command == "S:JOYSTICK_STATS") { + joystick_print_stats(); + return; + } + + // S:DUMPREGS [axisName] + // no argument -> dump all axes; with an argument (X/Y/Z/W) -> dump only the specified axis + // for diagnosing a freeze on-site: print the key TMC4361A registers to locate the ramp-generator-anomaly root cause + if (command.startsWith("S:DUMPREGS")) { + String filter = command.length() > 11 ? command.substring(11) : String(""); + filter.trim(); + char buf[160]; + for (uint8_t i = 0; i < axisManager.getAxisCount(); i++) { + Axis *axis = axisManager.getAxis(i); + if (!axis) continue; + const char *name = axis->getAxisName(); + if (filter.length() > 0 && filter != String(name)) continue; + + uint8_t icID = axis->getIcID(); + uint32_t status = tmc4361A_readRegister(icID, TMC4361A_STATUS); + uint32_t events = tmc4361A_readRegister(icID, TMC4361A_EVENTS); + uint32_t rampMode = tmc4361A_readRegister(icID, TMC4361A_RAMPMODE); + uint32_t refConf = tmc4361A_readRegister(icID, TMC4361A_REFERENCE_CONF); + int32_t xactual = (int32_t)tmc4361A_readRegister(icID, TMC4361A_XACTUAL); + int32_t xtarget = (int32_t)tmc4361A_readRegister(icID, TMC4361A_XTARGET); + int32_t vactual = (int32_t)tmc4361A_readRegister(icID, TMC4361A_VACTUAL); + int32_t vmax = (int32_t)tmc4361A_readRegister(icID, TMC4361A_VMAX); + int32_t vstopL = (int32_t)tmc4361A_readRegister(icID, TMC4361A_VIRT_STOP_LEFT); + int32_t vstopR = (int32_t)tmc4361A_readRegister(icID, TMC4361A_VIRT_STOP_RIGHT); + uint32_t stepConf = tmc4361A_readRegister(icID, TMC4361A_STEP_CONF); + + snprintf(buf, sizeof(buf), + "S:DUMP %s STATUS=0x%08lX EVENTS=0x%08lX RAMPMODE=0x%08lX", + name, (unsigned long)status, (unsigned long)events, + (unsigned long)rampMode); + SerialUSB.println(buf); + snprintf(buf, sizeof(buf), + "S:DUMP %s XACTUAL=%ld XTARGET=%ld VACTUAL=%ld VMAX=%ld", + name, (long)xactual, (long)xtarget, (long)vactual, (long)vmax); + SerialUSB.println(buf); + snprintf(buf, sizeof(buf), + "S:DUMP %s VSTOP_L=%ld VSTOP_R=%ld REFCONF=0x%08lX STEP_CONF=0x%08lX", + name, (long)vstopL, (long)vstopR, + (unsigned long)refConf, (unsigned long)stepConf); + SerialUSB.println(buf); + snprintf(buf, sizeof(buf), + "S:DUMP %s isMoving=%d state=%d softLimEn=%d needReenable=%d", + name, (int)axis->isMoving(), (int)axis->getCurrentState(), + (int)axis->isSoftLimitsEnabled(), 0); + SerialUSB.println(buf); + } + SerialUSB.println("S:DUMPREGS:END"); + return; + } + + // S:SET_HOMING_VEL + // for diagnostics: set homingVelocityMM at runtime without reflashing firmware + // e.g.: S:SET_HOMING_VEL Y 5.0 + if (command.startsWith("S:SET_HOMING_VEL")) { + String rest = command.substring(16); + rest.trim(); + int sp = rest.indexOf(' '); + if (sp < 0) { + SerialUSB.println("S:SET_HOMING_VEL:ERR:missing_args"); + return; + } + String axisName = rest.substring(0, sp); + String velStr = rest.substring(sp + 1); + axisName.trim(); + velStr.trim(); + float vel = velStr.toFloat(); + bool found = false; + for (uint8_t i = 0; i < axisManager.getAxisCount(); i++) { + Axis *axis = axisManager.getAxis(i); + if (!axis) continue; + if (axisName != String(axis->getAxisName())) continue; + axis->getMutableConfig().homingVelocityMM = vel; + char buf[80]; + snprintf(buf, sizeof(buf), "S:SET_HOMING_VEL:OK:%s=%.3f", axis->getAxisName(), vel); + SerialUSB.println(buf); + found = true; + break; + } + if (!found) { + SerialUSB.print("S:SET_HOMING_VEL:ERR:axis_not_found:"); + SerialUSB.println(axisName); + } + return; + } + + // handle other debug commands + DEBUG_PRINT("Serial:TO_AXISMGR:"); + DEBUG_PRINTLN(command); // debug point - dispatched to AxisManager + + bool success = axisManager.processCommand(command); + if (!success) { + sendDebugInfo("Command processing failed: %s", command.c_str()); + } + } +} + +void SerialProtocolHandler::processSerialStandardCommands() { + if (checkForCommand()) { + const byte *data = getCommandData(); + byte command = data[1]; + + switch (command) { + case Commands::MOVE_X: + commandProcessor.handleMoveX(data); + break; + + case Commands::MOVE_Y: + commandProcessor.handleMoveY(data); + break; + + case Commands::MOVE_Z: + commandProcessor.handleMoveZ(data); + break; + + case Commands::MOVE_THETA: + commandProcessor.handleMoveTheta(data); + break; + + case Commands::MOVE_W: + commandProcessor.handleMoveW(data); + break; + + case Commands::MOVE_W2: + commandProcessor.handleMoveW2(data); + break; + + case Commands::MOVE_TURRET: + commandProcessor.handleMoveTurret(data); + break; + + case Commands::MOVETO_TURRET: + commandProcessor.handleMoveToTurret(data); + break; + + case Commands::HOME_OR_ZERO: + commandProcessor.handleHomeOrZero(data); + break; + + case Commands::MOVETO_X: + commandProcessor.handleMoveToX(data); + break; + + case Commands::MOVETO_Y: + commandProcessor.handleMoveToY(data); + break; + + case Commands::MOVETO_Z: + commandProcessor.handleMoveToZ(data); + break; + + case Commands::SET_LIM: + commandProcessor.handleSetLim(data); + break; + + case Commands::TURN_ON_ILLUMINATION: + commandProcessor.handleTurnOnIllumination(data); + break; + + case Commands::TURN_OFF_ILLUMINATION: + commandProcessor.handleTurnOffIllumination(data); + break; + + case Commands::SET_ILLUMINATION: + commandProcessor.handleSetIllumination(data); + break; + + case Commands::SET_ILLUMINATION_LED_MATRIX: + commandProcessor.handleSetIlluminationLEDMatrix(data); + break; + + case Commands::ACK_JOYSTICK_BUTTON_PRESSED: + commandProcessor.handleAckJoystickButtonPressed(data); + break; + + case Commands::ANALOG_WRITE_ONBOARD_DAC: + commandProcessor.handleAnalogWriteOnboardDAC(data); + break; + + case Commands::SET_DAC80508_REFDIV_GAIN: + commandProcessor.handleSetDAC80508RefDivGain(data); + break; + + case Commands::SET_ILLUMINATION_INTENSITY_FACTOR: + commandProcessor.handleSetIlluminationIntensityFactor(data); + break; + + case Commands::SET_TRIGGER_MODE: + commandProcessor.handleSetTriggerMode(data); + break; + + case Commands::SET_PORT_INTENSITY: + commandProcessor.handleSetPortIntensity(data); + break; + + case Commands::TURN_ON_PORT: + commandProcessor.handleTurnOnPort(data); + break; + + case Commands::TURN_OFF_PORT: + commandProcessor.handleTurnOffPort(data); + break; + + case Commands::SET_PORT_ILLUMINATION: + commandProcessor.handleSetPortIllumination(data); + break; + + case Commands::SET_MULTI_PORT_MASK: + commandProcessor.handleSetMultiPortMask(data); + break; + + case Commands::TURN_OFF_ALL_PORTS: + commandProcessor.handleTurnOffAllPorts(data); + break; + + case Commands::SET_WATCHDOG_TIMEOUT: + commandProcessor.handleSetWatchdogTimeout(data); + break; + + case Commands::SET_PIN_LEVEL: + commandProcessor.handleSetPinLevel(data); + break; + + case Commands::HEARTBEAT: + commandProcessor.handleHeartbeat(data); + break; + + case Commands::MOVETO_W: + commandProcessor.handleMoveToW(data); + break; + + case Commands::SET_LIM_SWITCH_POLARITY: + commandProcessor.handleSetLimSwitchPolarity(data); + break; + + case Commands::CONFIGURE_STEPPER_DRIVER: + commandProcessor.handleConfigureStepperDriver(data); + break; + + case Commands::SET_MAX_VELOCITY_ACCELERATION: + commandProcessor.handleSetMaxVelocityAcceleration(data); + break; + + case Commands::SET_LEAD_SCREW_PITCH: + commandProcessor.handleSetLeadScrewPitch(data); + break; + + case Commands::SET_OFFSET_VELOCITY: + commandProcessor.handleSetOffsetVelocity(data); + break; + + case Commands::CONFIGURE_STAGE_PID: + commandProcessor.handleConfigureStagePID(data); + break; + + case Commands::ENABLE_STAGE_PID: + commandProcessor.handleEnableStagePID(data); + break; + + case Commands::DISABLE_STAGE_PID: + commandProcessor.handleDisableStagePID(data); + break; + + case Commands::SET_HOME_SAFETY_MERGIN: + commandProcessor.handleSetHomeSafetyMargin(data); + break; + + case Commands::SET_PID_ARGUMENTS: + commandProcessor.handleSetPIDArguments(data); + break; + + case Commands::SEND_HARDWARE_TRIGGER: + commandProcessor.handleSendHardwareTrigger(data); + break; + + case Commands::SET_STROBE_DELAY: + commandProcessor.handleSetStrobeDelay(data); + break; + + case Commands::SET_AXIS_DISABLE_ENABLE: + commandProcessor.handleSetAxisDisableEnable(data); + break; + + case Commands::INITFILTERWHEEL: + commandProcessor.handleInitFilterWheel(data); + break; + + case Commands::INITFILTERWHEEL_W2: + commandProcessor.handleInitFilterWheelW2(data); + break; + + case Commands::INITIALIZE: + commandProcessor.handleInitialize(data); + break; + + case Commands::RESET: + commandProcessor.handleReset(data); + break; + + default: + break; + } + } +} diff --git a/firmware/octoaxes/serial.h b/firmware/octoaxes/serial.h new file mode 100644 index 000000000..608798cc1 --- /dev/null +++ b/firmware/octoaxes/serial.h @@ -0,0 +1,82 @@ +#ifndef SERIAL_PROTOCOL_HANDLER_H +#define SERIAL_PROTOCOL_HANDLER_H + +#include + +class SerialProtocolHandler { +public: + SerialProtocolHandler(); + + // Initialize serial communication + void begin(long baudRate = 2000000, uint32_t timeout = 200); + + // Check for and process a new command + bool checkForCommand(); + + // Get the command ID + byte getCommandId() const { return cmd_id; } + + // Get the command execution status + bool isCommandInProgress() const { return mcu_cmd_execution_in_progress; } + + // Get the checksum-error status + bool hasChecksumError() const { return checksum_error; } + + // Get the received command data + const byte* getCommandData() const { return buffer_rx; } + + // Send a response message + void sendResponse(byte cmd_id, byte status, + int32_t x_pos, int32_t y_pos, int32_t z_pos, + int32_t w_pos = 0, + bool joystick_button_pressed = false); + + // 10ms periodic position reporting (called in loop()) + void send_position_update(); + + // Send debug info + void sendDebugInfo(const char* format, ...); + + // Set the command-execution-completed status + void setCommandInProgress(bool in_progress) { + mcu_cmd_execution_in_progress = in_progress; + } + + // Get the command length + static int getCommandLength() { return CMD_LENGTH; } + + // Get the message length + static int getMessageLength() { return MSG_LENGTH; } + + // Serial debug-info handler + void processSerialCommands(); + void processSerialDebugCommands(); + void processSerialStandardCommands(); + // CRC checksum function + uint8_t crc8ccitt(byte *data, uint8_t len); + +private: + static const int CMD_LENGTH = 8; + static const int MSG_LENGTH = 24; + + // Protocol identifiers + static const byte DEBUG_PROTOCOL_HEADER_1 = 0x55; + static const byte DEBUG_PROTOCOL_HEADER_2 = 0xAA; + + byte buffer_rx[512]; + volatile int buffer_rx_ptr; + byte cmd_id; + bool mcu_cmd_execution_in_progress; + bool checksum_error; + elapsedMicros _us_since_last_pos_update; + // the any_moving computed by the previous send_position_update, used to detect the falling edge (movement-complete edge) + // on the falling edge, immediately send an extra COMPLETED frame, saving the 0-10ms heartbeat wait + bool _last_any_moving = false; + + // Debug-command buffer + String debugCommandBuffer; +}; + +extern SerialProtocolHandler serialProtocol; + +#endif diff --git a/firmware/octoaxes/stepaxis.cpp b/firmware/octoaxes/stepaxis.cpp new file mode 100644 index 000000000..a1626a888 --- /dev/null +++ b/firmware/octoaxes/stepaxis.cpp @@ -0,0 +1,251 @@ +#include "stepaxis.h" +#include "build_opt.h" +#include "tmc/ic/TMC4361A/TMC4361A.h" + +StepAxis::StepAxis(uint8_t csPin, uint8_t axisIndex, const char* axisName) + : Axis(csPin, axisIndex, axisName) { + _backlashMM = 0.0f; + _backlashCompensationEnabled = false; +} + +bool StepAxis::begin(const AxisConfig& config) { + // call the base-class init + bool result = Axis::begin(config); + + if (result) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":StepAxis initialized successfully"); + } + + return result; +} + +void StepAxis::setBacklashCompensation(float backlashMM) { + _backlashMM = backlashMM; + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Backlash compensation set to "); + DEBUG_PRINTF(backlashMM, 3); + DEBUG_PRINTLN("mm"); +} + +void StepAxis::enableBacklashCompensation(bool enable) { + _backlashCompensationEnabled = enable; + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Backlash compensation "); + DEBUG_PRINTLN(enable ? "enabled" : "disabled"); +} + +bool StepAxis::moveToPosition(float positionMM) { + // in the stepper axis, backlash-compensation logic can be added + if (_backlashCompensationEnabled && _backlashMM > 0) { + // compute the movement direction + float currentPos = getCurrentPositionMM(); + int32_t direction = (positionMM > currentPos) ? 1 : -1; + + // apply backlash compensation + applyBacklashCompensation(direction); + } + + // call the base-class move function + return Axis::moveToPosition(positionMM); +} + +bool StepAxis::moveRelative(float distanceMM) { + // in the stepper axis, backlash-compensation logic can be added + if (_backlashCompensationEnabled && _backlashMM > 0) { + int32_t direction = (distanceMM > 0) ? 1 : -1; + applyBacklashCompensation(direction); + } + + // call the base-class move function + return Axis::moveRelative(distanceMM); +} + +void StepAxis::applyBacklashCompensation(int32_t direction) { + // a simple backlash-compensation implementation + // real applications may need more complex logic + if (_backlashMM > 0) { + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Applying backlash compensation: "); + DEBUG_PRINTF(_backlashMM, 3); + DEBUG_PRINTLN("mm"); + + // first move in the opposite direction to take up the backlash, then move toward the target + float compensationDistance = direction * _backlashMM; + Axis::moveRelative(compensationDistance); + + // wait for the compensation move to complete + while (isMoving()) { + delay(10); + } + } +} + +bool StepAxis::handleSetLimits(const String& command) { + int space1 = command.indexOf(' '); + int space2 = command.indexOf(' ', space1 + 1); + int space3 = command.indexOf(' ', space2 + 1); + + if (space1 == -1 || space2 == -1 || space3 == -1) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":SET_LIMITS ERROR: Invalid format"); + return false; + } + + String s_down_limit = command.substring(space2 + 1, space3); + String s_up_limit = command.substring(space3 + 1); + + int32_t down_limit = hexStringToInt32(s_down_limit); + int32_t up_limit = hexStringToInt32(s_up_limit); + + float lowerLimitMM = down_limit / 1000.0; + float upperLimitMM = up_limit / 1000.0; + + DEBUG_PRINT("1.LowLimit: "); + DEBUG_PRINTLN(lowerLimitMM); + + DEBUG_PRINT("2.UpperLimit: "); + DEBUG_PRINTLN(upperLimitMM); + + setSoftLimits(lowerLimitMM, upperLimitMM); + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":SET_LIMITS OK"); + return true; +} + + +void StepAxis::performHomingSequence() { + if (checkTimeout(_homing_timeout_ms)) { + restoreNormalMicrosteps(); + handleError("Homing timeout"); + return; + } + + uint8_t limit_state = readLimitSwitches(); + + switch (_currentState) { + case STATE_HOMING_INIT: + // directly disable the virtual limits in hardware without changing the _softLimitsEnabled flag + motor_enableSoftLimits(_icID, false, false); + + // unlock the hard-stop latch: reuse the full, already-verified + // VSTOP recovery path of motor_moveToMicrosteps (disable EN -> clear EVENTS -> write XTARGET -> clear EVENTS again). + // + // scenario: after a firmware reset XACTUAL=0, SET_LIM x_neg=5mm immediately triggers a VSTOPL_ACTIVE_F + // hard-stop, locking the chip ramp generator. A subsequent motor_setVelocityInternal + // only writes VMAX and cannot release the hard-stop latch, so the motor does not move. + // + // writing XTARGET=XACTUAL causes no movement, only triggers the chip to re-evaluate the ramp state, + // resetting the hard-stop latch. This is the VSTOP recovery path of motor_moveToMicrosteps, + // already verified effective in the 2026-02-27 commit. + motor_moveToMicrosteps(_icID, motor_getPositionMicrosteps(_icID)); + + switchToHomingMicrosteps(); + + if (limit_state & _config.homingSwitch) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Already at home position, moving away first..."); + setState(STATE_LEAVING_HOME); + } else { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Starting homing process..."); + int32_t speedInternal = _config.homing_direct * motor_velocityMMToInternal(_icID, _config.homingVelocityMM); + motor_setVelocityInternal(_icID, speedInternal); + setState(STATE_HOMING_SEARCH); + } + break; + + case STATE_HOMING_SEARCH: + if (limit_state & _config.homingSwitch) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Home limit switch triggered!"); + + motor_setVelocityInternal(_icID, 0); // stop + delay(100); // wait for a full stop + + int32_t latchedPosition = motor_readLatchPosition(_icID); + + // compute the safe position (away from the limit switch) + int32_t safePosition = latchedPosition; + int32_t margin = motor_mmToMicrosteps(_icID, _config.homeSafetyPositionMM); + // retract direction = opposite of the search direction (homing_direct), always leaving the limit just hit. + // more robust than "homingSwitch +/- margin": when homingSwitch and the search direction do not follow the usual convention + // (e.g. new Z: LEFT_SW but homing_direct=+1 toward physical left, the left limit is at the firmware positive-direction end), + // the old logic would retract deeper into the limit -> unable to leave the sensing zone. Equivalent for regular X/Y/Z (no regression). + safePosition -= _config.homing_direct * margin; + + DEBUG_PRINT(_axisName); + DEBUG_PRINT(":Moving to safe position: "); + DEBUG_PRINTLN(safePosition); + + motor_moveToMicrosteps(_icID, safePosition); + _checkHomeReachTimeout = 0; + + setState(STATE_HOMING_SET_ZERO); + } + break; + + case STATE_HOMING_SET_ZERO: + // wait for the move to the safe position to complete (timeout 5 seconds = 5,000,000 microseconds) + if (isMovementComplete() || _checkHomeReachTimeout >= 5000000) { + // restore normal microstepping + restoreNormalMicrosteps(); + // set the current position to 0 + motor_setCurrentPositionMicrosteps(_icID, 0); + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Homing completed! Current position set to 0"); + if (_checkHomeReachTimeout >= 5000000) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Homing Set Current Position to safe position Timeout"); + } + // restore the pre-homing soft-limit state (the host set the VIRT_STOP values during initialization) + if (_softLimitsEnabled) { + enableSoftLimits(true); + } + + // automatically restore PID after homing completes (consistent with the old architecture) + if (_pidState.enabled) { + motor_enablePID(_icID); + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":PID re-enabled after homing"); + } + + setState(STATE_IDLE); + } + break; + + default: + break; + } +} + +void StepAxis::performLeavingHome() { + if (checkTimeout(LEAVING_HOME_TIMEOUT_MS)) { + handleError("Leaving home timeout"); + return; + } + + uint8_t limit_state = readLimitSwitches(); + + if (_currentState == STATE_LEAVING_HOME) { + if (!(limit_state & _config.homingSwitch)) { + DEBUG_PRINT(_axisName); + DEBUG_PRINTLN(":Left home position, starting homing..."); + motor_setVelocityInternal(_icID, 0); // stop + + // wait for a full stop + delay(100); + + // start the actual homing search + int32_t speedInternal = _config.homing_direct * motor_velocityMMToInternal(_icID, _config.maxVelocityMM); + motor_setVelocityInternal(_icID, speedInternal); + setState(STATE_HOMING_SEARCH); + } else { + // keep moving to leave the home position + // set the correct leaving direction based on the limit-switch type + int32_t speedInternal = -1 * _config.homing_direct * motor_velocityMMToInternal(_icID, _config.maxVelocityMM); + motor_setVelocityInternal(_icID, speedInternal); + } + } +} + diff --git a/firmware/octoaxes/stepaxis.h b/firmware/octoaxes/stepaxis.h new file mode 100644 index 000000000..a28b697a0 --- /dev/null +++ b/firmware/octoaxes/stepaxis.h @@ -0,0 +1,36 @@ +#ifndef STEP_AXIS_H +#define STEP_AXIS_H + +#include "axis.h" + +class StepAxis : public Axis { +public: + // Constructor + StepAxis(uint8_t csPin, uint8_t axisIndex, const char* axisName); + + // Override the base-class init function to add stepper-axis-specific configuration + bool begin(const AxisConfig& config) override; + + // Stepper-axis-specific features + void setBacklashCompensation(float backlashMM); + void enableBacklashCompensation(bool enable); + + // Override the motion-control functions to add stepper-axis-specific logic + bool moveToPosition(float positionMM) override; + bool moveRelative(float distanceMM) override; + + virtual bool handleSetLimits(const String& command) override; + +private: + float _backlashMM; + bool _backlashCompensationEnabled; + + // Stepper-axis-specific methods + void applyBacklashCompensation(int32_t direction); + + void performHomingSequence() override; + void performLeavingHome() override; +}; + +#endif + diff --git a/firmware/octoaxes/tmc/hal/TMC_SPI.cpp b/firmware/octoaxes/tmc/hal/TMC_SPI.cpp new file mode 100644 index 000000000..db98ae062 --- /dev/null +++ b/firmware/octoaxes/tmc/hal/TMC_SPI.cpp @@ -0,0 +1,240 @@ +/* + * TMC_SPI.cpp + * + * Implementation of SPI Hardware Abstraction Layer for TMC ICs. + * + * Created: 2026-01-21 + */ + +#include "TMC_SPI.h" +#include +#include + +#ifdef USE_HC154_CS +// octoaxesplus (squid++ dual-camera): the csPin field semantics change to a 74HC154 channel number (0-15) +// before a transaction call Pins::hc154_select(ch) to select the target channel; after the transaction return to the idle channel +// Note: tmc/ is a symlink from octoaxesplus to octoaxes; the relative path "../../config.h" +// after symlink resolution points to octoaxes/config.h (no HC154 symbols), +// so use a bare include and rely on the PlatformIO src_dir search path +#include "config.h" +#endif + +// ============================================================================ +// Configuration Constants (from config.h Pins namespace) +// ============================================================================ + +#ifndef USE_HC154_CS +// octoaxes direct GPIO CS +#define PIN_CS_X 41 +#define PIN_CS_Y 36 +#define PIN_CS_Z 35 +#define PIN_CS_W 34 +#define PIN_CS_W2 16 // 2026-05-26 W2 reuses the original EXPAND4 hardware (CS=pin 16, CLK=pin 28), + // fully consistent with legacy Squid pin_TMC4361_CS[4]=16 / pin_TMC4361_CLK_W2=28 +#define PIN_CS_E1 19 // 2026-05-29 E1 objective turret (CS=pin 19 = EXPAND1_AXIS_CS, CLK=pin 28) +#endif + +// Clock source identifiers +#define CLOCK_STANDARD 0 // Pin 37 +#define CLOCK_EXPAND 1 // Pin 28 + +// SPI Configuration +#define TMC_SPI_SPEED 500000 // 500 kHz +#define TMC_SPI_MODE SPI_MODE0 // CPOL=0, CPHA=0 +#define TMC_SPI_BIT_ORDER MSBFIRST +#define TMC_CS_DELAY_US 100 // Delay after CS low + +// ============================================================================ +// IC Configuration Array +// ============================================================================ + +const TMC_IC_Config tmc_ic_configs[TMC4361A_IC_COUNT] = { + // Note: the order must match the axisManager.addAxis() call order! +#ifdef USE_HC154_CS + // squid++ XYZW1W2 + E1 six axes: addAxis order Y(0), X(1), Z(2), W1(3), W2(4), E1(5) + // single clock set (EXPAND_CLK removed), everything uses CLOCK_STANDARD + // 2026-06-02 E1 objective turret enabled: the icID=5 slot connects to HC154_AXIS_R (ch3); icID 6-7 are still placeholders + { .csPin = (uint8_t)Pins::HC154_AXIS_Y, .clockSource = CLOCK_STANDARD }, // icID=0 + { .csPin = (uint8_t)Pins::HC154_AXIS_X, .clockSource = CLOCK_STANDARD }, // icID=1 + { .csPin = (uint8_t)Pins::HC154_AXIS_Z1, .clockSource = CLOCK_STANDARD }, // icID=2 (axisName="Z") + { .csPin = (uint8_t)Pins::HC154_AXIS_W1, .clockSource = CLOCK_STANDARD }, // icID=3 (ch6, uses the original Z2 CS) + { .csPin = (uint8_t)Pins::HC154_AXIS_W2, .clockSource = CLOCK_STANDARD }, // icID=4 (ch4, uses the original T CS) + { .csPin = (uint8_t)Pins::HC154_AXIS_R, .clockSource = CLOCK_STANDARD }, // icID=5 (ch3, objective turret axisName="Turret") + { .csPin = (uint8_t)Pins::HC154_AXIS_F2, .clockSource = CLOCK_STANDARD }, // icID=6 placeholder + { .csPin = (uint8_t)Pins::HC154_AXIS_F1, .clockSource = CLOCK_STANDARD }, // icID=7 placeholder +#else + // octoaxes 6 axes: add order Y(0), X(1), Z(2), W(3), W2(4), E1(5) + // W2/E1 use CLOCK_EXPAND (pin 28); W2 is consistent with legacy Squid pin_TMC4361_CLK_W2, + // E1 (objective) shares the same expansion clock line (multiple TMC4361A chips can share a clock) + { .csPin = PIN_CS_Y, .clockSource = CLOCK_STANDARD }, + { .csPin = PIN_CS_X, .clockSource = CLOCK_STANDARD }, + { .csPin = PIN_CS_Z, .clockSource = CLOCK_STANDARD }, + { .csPin = PIN_CS_W, .clockSource = CLOCK_STANDARD }, + { .csPin = PIN_CS_W2, .clockSource = CLOCK_EXPAND }, + { .csPin = PIN_CS_E1, .clockSource = CLOCK_EXPAND }, // icID=5 objective turret +#endif +}; + +// ============================================================================ +// Debug Status Storage (Optional) +// ============================================================================ + +#ifdef TMC_SPI_DEBUG +static uint8_t tmc_lastStatus[TMC4361A_IC_COUNT] = {0}; +static uint32_t tmc_transferCount[TMC4361A_IC_COUNT] = {0}; +#endif + +// ============================================================================ +// Initialization +// ============================================================================ + +void tmc_spi_init(void) +{ +#ifdef USE_HC154_CS + // 74HC154 address-pin init; all channel outputs default to 0 + Pins::hc154_init(); +#else + // Initialize all CS pins as OUTPUT and set HIGH (inactive) + for (uint8_t i = 0; i < TMC4361A_IC_COUNT; i++) { + pinMode(tmc_ic_configs[i].csPin, OUTPUT); + digitalWrite(tmc_ic_configs[i].csPin, HIGH); + } +#endif + + // Initialize SPI bus + SPI.begin(); +} + +// ============================================================================ +// TMC4361A SPI Callbacks +// ============================================================================ + +void tmc4361A_readWriteSPI(uint16_t icID, uint8_t *data, size_t dataLength) +{ + // Validate IC ID + if (icID >= TMC4361A_IC_COUNT) { + return; + } + + uint8_t csPin = tmc_ic_configs[icID].csPin; + + // Begin SPI transaction + SPI.beginTransaction(SPISettings(TMC_SPI_SPEED, TMC_SPI_BIT_ORDER, TMC_SPI_MODE)); + +#ifdef USE_HC154_CS + // 74HC154: select the target channel; the other channels are automatically pulled high (always exactly one low) + Pins::hc154_select(csPin); +#else + // Assert CS (active low) + digitalWrite(csPin, LOW); +#endif + + // Wait for chip ready + delayMicroseconds(TMC_CS_DELAY_US); + + // Full-duplex transfer + for (size_t i = 0; i < dataLength; i++) { + data[i] = SPI.transfer(data[i]); + } + +#ifdef USE_HC154_CS + // return to EXPAND_NSCS1 (a placeholder channel with no SPI device attached) + Pins::hc154_select((uint8_t)Pins::HC154_EXPAND_NSCS1); +#else + // Deassert CS + digitalWrite(csPin, HIGH); +#endif + + // End SPI transaction + SPI.endTransaction(); + +#ifdef TMC_SPI_DEBUG + tmc_transferCount[icID]++; +#endif +} + +void tmc4361A_setStatus(uint16_t icID, uint8_t *data) +{ + // Validate IC ID + if (icID >= TMC4361A_IC_COUNT) { + return; + } + +#ifdef TMC_SPI_DEBUG + // Store status byte (first byte of response) + tmc_lastStatus[icID] = data[0]; +#endif + + // Status byte interpretation (for future error handling): + // Bit 7: RESET_FLAG - Indicates reset occurred + // Bit 6: DRV_ERR - Driver error + // Bit 5: UV_SF - Undervoltage + // Bit 4-0: Various status flags + + // Currently just store for debugging, can be extended for error handling + (void)data; // Suppress unused parameter warning if debug disabled +} + +// ============================================================================ +// TMC2660 SPI Callbacks (Reserved) +// ============================================================================ + +void tmc2660_readWriteSPI(uint16_t icID, uint8_t *data, size_t dataLength) +{ + // Reserved for direct SPI communication with TMC2660 + // Currently TMC2660 is controlled through TMC4361A Cover interface + // + // If direct SPI is needed in the future, implement similar to tmc4361A_readWriteSPI + // but with TMC2660-specific timing and data format (20-bit datagrams) + + (void)icID; + (void)data; + (void)dataLength; +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +uint8_t tmc_getCSPin(uint16_t icID) +{ + if (icID >= TMC4361A_IC_COUNT) { + return 0xFF; // Invalid + } + return tmc_ic_configs[icID].csPin; +} + +uint8_t tmc_getClockSource(uint16_t icID) +{ + if (icID >= TMC4361A_IC_COUNT) { + return 0xFF; // Invalid + } + return tmc_ic_configs[icID].clockSource; +} + +bool tmc_isValidICID(uint16_t icID) +{ + return (icID < TMC4361A_IC_COUNT); +} + +// ============================================================================ +// Debug Functions (Optional) +// ============================================================================ + +#ifdef TMC_SPI_DEBUG +uint8_t tmc_getLastStatus(uint16_t icID) +{ + if (icID >= TMC4361A_IC_COUNT) { + return 0xFF; + } + return tmc_lastStatus[icID]; +} + +uint32_t tmc_getTransferCount(uint16_t icID) +{ + if (icID >= TMC4361A_IC_COUNT) { + return 0; + } + return tmc_transferCount[icID]; +} +#endif diff --git a/firmware/octoaxes/tmc/hal/TMC_SPI.h b/firmware/octoaxes/tmc/hal/TMC_SPI.h new file mode 100644 index 000000000..2af09e84a --- /dev/null +++ b/firmware/octoaxes/tmc/hal/TMC_SPI.h @@ -0,0 +1,175 @@ +/* + * TMC_SPI.h + * + * Hardware Abstraction Layer for TMC SPI communication. + * Provides SPI callback functions for TMC4361A and TMC2660 drivers. + * + * Created: 2026-01-21 + */ + +#ifndef TMC_SPI_H_ +#define TMC_SPI_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================ +// IC Count Configuration +// ============================================================================ + +#ifdef USE_HC154_CS +// squid++ dual-camera 8 axes: Y, X, Z1, F1, Z2, F2, R, T +#define TMC4361A_IC_COUNT 8 +#define TMC2660_IC_COUNT 8 +#else +// octoaxes 6 axes: Y, X, Z, W, W2, E1 (2026-05-26 W2 took over the original E4 hardware CS=pin 16/CLK=pin 28; +// 2026-05-29 E1 enabled as the objective turret (Objectives), CS=pin 19/CLK=pin 28; the original E3 is not enabled) +#define TMC4361A_IC_COUNT 6 +#define TMC2660_IC_COUNT 6 +#endif + +// ============================================================================ +// IC Identifier Enumeration +// ============================================================================ + +typedef enum { + // Note: icID is actually determined by the addAxis() call order (see TMC_SPI.cpp tmc_ic_configs[]). + // current octoaxes order: X(0) Y(1) Z(2) W(3) W2(4) E1(5). The enum below is for semantic reference only. + IC_X = 0, // X axis + IC_Y = 1, // Y axis + IC_Z = 2, // Z axis + IC_W = 3, // W axis (Filter wheel 1) + IC_W2 = 4, // W2 axis (Filter wheel 2, took over the original E4 hardware) + IC_E1 = 5 // Expand 1 (Objectives, objective turret) +} TMC_IC_ID; + +// ============================================================================ +// IC Configuration Structure +// ============================================================================ + +typedef struct { + uint8_t csPin; // Chip select pin number + uint8_t clockSource; // 0 = standard clock (Pin 37), 1 = expand clock (Pin 28) +} TMC_IC_Config; + +// ============================================================================ +// Global IC Configuration Array (defined in TMC_SPI.cpp) +// ============================================================================ + +extern const TMC_IC_Config tmc_ic_configs[TMC4361A_IC_COUNT]; + +// ============================================================================ +// Initialization +// ============================================================================ + +/** + * @brief Initialize SPI and all CS pins + * + * Configures all CS pins as OUTPUT and sets them HIGH. + * Initializes SPI bus with appropriate settings. + */ +void tmc_spi_init(void); + +// ============================================================================ +// TMC4361A SPI Callbacks +// ============================================================================ + +/** + * @brief SPI read/write callback for TMC4361A + * + * This function is called by the TMC4361A driver to perform SPI transfers. + * It handles CS pin control and full-duplex data transfer. + * + * @param icID IC identifier (0 to TMC4361A_IC_COUNT-1) + * @param data Buffer for data to send/receive (modified in place) + * @param dataLength Number of bytes to transfer + */ +void tmc4361A_readWriteSPI(uint16_t icID, uint8_t *data, size_t dataLength); + +/** + * @brief Status callback for TMC4361A + * + * Called after each SPI transfer to process the status byte. + * Can be used for diagnostics and error monitoring. + * + * @param icID IC identifier + * @param data Buffer containing the response data (first byte is status) + */ +void tmc4361A_setStatus(uint16_t icID, uint8_t *data); + +// ============================================================================ +// TMC2660 SPI Callbacks (for direct SPI mode, reserved for future use) +// ============================================================================ + +/** + * @brief SPI read/write callback for TMC2660 (direct SPI mode) + * + * Reserved for future use. Currently TMC2660 is controlled through + * TMC4361A Cover interface. + * + * @param icID IC identifier + * @param data Buffer for data to send/receive + * @param dataLength Number of bytes to transfer + */ +void tmc2660_readWriteSPI(uint16_t icID, uint8_t *data, size_t dataLength); + +// ============================================================================ +// Utility Functions +// ============================================================================ + +/** + * @brief Get CS pin for a given IC ID + * + * @param icID IC identifier + * @return CS pin number, or 0xFF if invalid + */ +uint8_t tmc_getCSPin(uint16_t icID); + +/** + * @brief Get clock source for a given IC ID + * + * @param icID IC identifier + * @return 0 for standard clock, 1 for expand clock, 0xFF if invalid + */ +uint8_t tmc_getClockSource(uint16_t icID); + +/** + * @brief Check if IC ID is valid + * + * @param icID IC identifier + * @return true if valid, false otherwise + */ +bool tmc_isValidICID(uint16_t icID); + +// ============================================================================ +// Debug/Status (Optional) +// ============================================================================ + +#ifdef TMC_SPI_DEBUG +/** + * @brief Get last SPI status byte for an IC + * + * @param icID IC identifier + * @return Last status byte received + */ +uint8_t tmc_getLastStatus(uint16_t icID); + +/** + * @brief Get SPI transfer count for an IC + * + * @param icID IC identifier + * @return Number of SPI transfers performed + */ +uint32_t tmc_getTransferCount(uint16_t icID); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* TMC_SPI_H_ */ diff --git a/firmware/octoaxes/tmc/helpers/API_Header.h b/firmware/octoaxes/tmc/helpers/API_Header.h new file mode 100644 index 000000000..839bfd15b --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/API_Header.h @@ -0,0 +1,40 @@ +/* + * tmc_header.h + * + * Created on: 29.09.2016 + * Author: ed + */ + +#ifndef TMC_API_HEADER_H_ +#define TMC_API_HEADER_H_ + +#include "Config.h" +#include "Macros.h" +#include "Constants.h" +#include "Bits.h" +#include "CRC.h" +#include "RegisterAccess.h" +#include +#include "Types.h" + +// TODO: Restructure these. +/* + * Goal: Just give these values here as status back to the IDE when used with EvalSystem. + * Currently, this is obtained by just leaving out implementation specific error bits here. + */ +typedef enum { + TMC_ERROR_NONE = 0x00, + TMC_ERROR_GENERIC = 0x01, + TMC_ERROR_FUNCTION = 0x02, + TMC_ERROR_MOTOR = 0x08, + TMC_ERROR_VALUE = 0x10, + TMC_ERROR_CHIP = 0x40 +} TMCError; + +typedef enum { + TMC_COMM_DEFAULT, + TMC_COMM_SPI, + TMC_COMM_UART +} TMC_Comm_Mode; + +#endif /* TMC_API_HEADER_H_ */ diff --git a/firmware/octoaxes/tmc/helpers/Bits.h b/firmware/octoaxes/tmc/helpers/Bits.h new file mode 100644 index 000000000..ad70faecb --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/Bits.h @@ -0,0 +1,85 @@ +// BIT DEFINITION +#ifndef TMC_BITS_H_ +#define TMC_BITS_H_ + +#define BIT0 0x00000001 +#define BIT1 0x00000002 +#define BIT2 0x00000004 +#define BIT3 0x00000008 +#define BIT4 0x00000010 +#define BIT5 0x00000020 +#define BIT6 0x00000040 +#define BIT7 0x00000080 +#define BIT8 0x00000100 +#define BIT9 0x00000200 +#define BIT10 0x00000400 +#define BIT11 0x00000800 +#define BIT12 0x00001000 +#define BIT13 0x00002000 +#define BIT14 0x00004000 +#define BIT15 0x00008000 +#define BIT16 0x00010000 +#define BIT17 0x00020000 +#define BIT18 0x00040000 +#define BIT19 0x00080000 +#define BIT20 0x00100000 +#define BIT21 0x00200000 +#define BIT22 0x00400000 +#define BIT23 0x00800000 +#define BIT24 0x01000000 +#define BIT25 0x02000000 +#define BIT26 0x04000000 +#define BIT27 0x08000000 +#define BIT28 0x10000000 +#define BIT29 0x20000000 +#define BIT30 0x40000000 +#define BIT31 0x80000000 + +#define BYTE0_MASK 0x00000000000000FF +#define BYTE0_SHIFT 0 +#define BYTE1_MASK 0x000000000000FF00 +#define BYTE1_SHIFT 8 +#define BYTE2_MASK 0x0000000000FF0000 +#define BYTE2_SHIFT 16 +#define BYTE3_MASK 0x00000000FF000000 +#define BYTE3_SHIFT 24 +#define BYTE4_MASK 0x000000FF00000000 +#define BYTE4_SHIFT 32 +#define BYTE5_MASK 0x0000FF0000000000 +#define BYTE5_SHIFT 40 +#define BYTE6_MASK 0x00FF000000000000 +#define BYTE6_SHIFT 48 +#define BYTE7_MASK 0xFF00000000000000 +#define BYTE7_SHIFT 56 + +#define SHORT0_MASK (BYTE0_MASK|BYTE1_MASK) +#define SHORT0_SHIFT BYTE0_SHIFT +#define SHORT1_MASK (BYTE2_MASK|BYTE3_MASK) +#define SHORT1_SHIFT BYTE2_SHIFT +#define SHORT2_MASK (BYTE4_MASK|BYTE5_MASK) +#define SHORT2_SHIFT BYTE4_SHIFT +#define SHORT3_MASK (BYTE6_MASK|BYTE7_MASK) +#define SHORT3_SHIFT BYTE6_SHIFT + +#define WORD0_MASK (SHORT0_MASK|SHORT1_MASK) +#define WORD0_SHIFT SHORT0_SHIFT +#define WORD1_MASK (SHORT2_MASK|SHORT3_MASK) +#define WORD1_SHIFT SHORT2_SHIFT + +#define NIBBLE(value, n) (((value) >> ((n) << 2)) & 0x0F) +#define BYTE(value, n) (((value) >> ((n) << 3)) & 0xFF) +#define SHORT(value, n) (((value) >> ((n) << 4)) & 0xFFFF) +#define WORD(value, n) (((value) >> ((n) << 5)) & 0xFFFFFFFF) + +#define _8_16(__1, __0) (((__1) << BYTE1_SHIFT) | ((__0) << BYTE0_SHIFT)) + +#define _8_32(__3, __2, __1, __0) (((__3) << BYTE3_SHIFT) | ((__2) << BYTE2_SHIFT) | ((__1) << BYTE1_SHIFT) | ((__0) << BYTE0_SHIFT)) +#define _16_32(__1, __0) (((__1) << SHORT1_SHIFT) | ((__0) << SHORT0_SHIFT)) + +#define _8_64(__7, __6, __5, __4, __3, __2, __1, __0) (((__7) << BYTE7_SHIFT) | ((__6) << BYTE6_SHIFT) | ((__5) << BYTE5_SHIFT) | ((__4) << BYTE4_SHIFT) | ((__3) << BYTE3_SHIFT) | ((__2) << BYTE2_SHIFT) | ((__1) << BYTE1_SHIFT) | ((__0) << BYTE0_SHIFT)) +#define _16_64(__3, __2, __1, __0) (((__3) << SHORT3_SHIFT) | ((__2) << SHORT2_SHIFT) | ((__1) << SHORT1_SHIFT) | ((__0) << SHORT0_SHIFT)) +#define _32_64(__1, __0) (((__1) << WORD1_SHIFT) | ((__0) << WORD0_SHIFT)) + +#define SIGN_EXTEND(base, bit, extended) ((extended) ((base) | ((((extended)(base)) & (1 << (bit))) << ((sizeof(extended) * 8) - (bit) - 1)))) + +#endif /* TMC_BITS_H_ */ diff --git a/firmware/octoaxes/tmc/helpers/CRC.c b/firmware/octoaxes/tmc/helpers/CRC.c new file mode 100644 index 000000000..8933fa18e --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/CRC.c @@ -0,0 +1,211 @@ +/* + * CRC.c + * + * Created on: 04.12.2017 + * Author: LH + * + * This is a generic implementation for a CRC8 generator supporting + * both compile-time (1) and run-time initialized Lookup tables for efficient CRC8 calculation. + * You can store multiple tables for different polynomials and (non-)reflected CRCs. + * The different tables are referenced by an index, with an upper limit set at compile time (CRC_TABLE_COUNT). + * + * To generate CRCs you must first generate the Lookup-table by calling fillCRCTable() + * with any index. CRCs can then be generated from any data buffer by calling CRC() + * with the same index previously given to fillCRCTable(). + * + * The table generation has been optimized for speed so that the runtime + * table generation can even be done during normal operation if required. + * However, as long as the required polynomials are known on initialization, + * the table generation should be done at that time. + * On the Landungsbruecke the initialization of a CRC table takes ~250µs. (2) + * Should your application still have problems with the table calculation time, + * this algorithm could probably be speed up by preparing a 2- or 4-bit lookup table + * to speed up the actual table generation. + * + * (1): For compile-time CRC tables, just fill the table(s) by initializing CRCTables[] to the proper values. + * (2): Tested by toggling a GPIO pin, generating a table in-between and measuring the GPIO pulse width. + */ + +#include "CRC.h" + +typedef struct { + uint8_t table[256]; + uint8_t polynomial; + bool isReflected; +} CRCTypeDef; + +CRCTypeDef CRCTables[CRC_TABLE_COUNT] = { 0 }; + +static uint8_t flipByte(uint8_t value); +static uint32_t flipBitsInBytes(uint32_t value); + +/* This function generates the Lookup table used for CRC calculations. + * + * Arguments: + * uint8_t polynomial: The CRC polynomial for which the table will be generated. + * bool isReflected: Indicator whether the CRC table will be reflected or not. + * uint8_t index: The index of the table to be filled. + * + * How it works: + * A CRC calculation of a byte can be done by taking the byte to be CRC'd, + * shifting it left by one (appending a 0) and - if a 1 has been shifted out - + * XOR-ing in the CRC polynomial. After 8 iterations the result will be the + * CRC of the Byte. + * + * The function below does this in a compact way, by using all 4 bytes of a + * uint32_t to do 4 separate CRC bytes at once. + * For this to work without the Byte shifting interfering with adjacent bytes, + * the polynomial has the 8th bit (0x100) set. That way, if the shifted-out bit + * is 1, the following XOR-ing with the CRC polynomial will set that 1 to a 0, + * resulting in the shifted-in 0 for the adjacent byte. + * This process will go from the the lowest to the highest byte, resulting in + * fully independent byte-wise CRC calculations. For the highest byte, the value + * of the shifted-out byte needs to be stored before shifting the bytes (isMSBSet). + * + * The for-loop that iterates over all uint8_t values starts out with the + * uint8_t values 3 to 0 stored in one uint32_t: 0x03020100 + * for each iteration each uint8_t value will increase by 4.. + * 0 -> 4 -> 8 -> C -> ... + * 1 -> 5 -> 9 -> D -> ... + * 2 -> 6 -> A -> E -> ... + * 3 -> 7 -> B -> F -> ... + * ..resulting in an increase of the uint32_t by 0x04040404: + * 0x03020100 -> 0x07060504 -> 0x0B0A0908 -> 0x0F0E0D0C -> ... + * The loop ends as soon as we have iterated over all uint8_t values. + * We detect that by looking for the byte-wise overflow into the next byte: + * 0xFFFEFDFC <- last uint32_t value to be calculated + * 0xFF, 0xFE, 0xFD, 0xFC <- the corresponding uint8_t values + * 0x103, 0x102, 0x101, 0x100 <- incremented uint8_t values (overflow into the next byte!) + * 0x04030200 <- uint32_t value with the overflowed bytes + * + * We have the lower uint8_t values at the lower bytes of the uint32_t. + * This allows us to simply store the lowest byte of the uint32_t, + * right-shift the uint32_t by 8 and increment the table pointer. + * After 4 iterations of that all 4 bytes of the uint32_t are stored in the table. + */ +uint8_t tmc_fillCRC8Table(uint8_t polynomial, bool isReflected, uint8_t index) +{ + uint32_t CRCdata; + // Helper pointer for traversing the result table + uint8_t *table; + + if(index >= CRC_TABLE_COUNT) + return 0; + + CRCTables[index].polynomial = polynomial; + CRCTables[index].isReflected = isReflected; + table = &CRCTables[index].table[0]; + + // Extend the polynomial to correct byte MSBs shifting into next bytes + uint32_t poly = (uint32_t) polynomial | 0x0100; + + // Iterate over all 256 possible uint8_t values, compressed into a uint32_t (see detailed explanation above) + uint32_t i; + for(i = 0x03020100; i != 0x04030200; i+=0x04040404) + { + // For reflected table: Flip the bits of each input byte + CRCdata = (isReflected)? flipBitsInBytes(i) : i; + + // Iterate over 8 Bits + int j; + for(j = 0; j < 8; j++) + { + // Store value of soon-to-be shifted out byte + uint8_t isMSBSet = (CRCdata & 0x80000000)? 1:0; + + // CRC Shift + CRCdata <<= 1; + + // XOR the bytes when required, lowest to highest + CRCdata ^= (CRCdata & 0x00000100)? (poly ) : 0; + CRCdata ^= (CRCdata & 0x00010000)? (poly << 8 ) : 0; + CRCdata ^= (CRCdata & 0x01000000)? (poly << 16) : 0; + CRCdata ^= (isMSBSet)? (poly << 24) : 0; + } + + // For reflected table: Flip the bits of each output byte + CRCdata = (isReflected)? flipBitsInBytes(CRCdata) : CRCdata; + // Store the CRC result bytes in the table array + *table++ = (uint8_t) CRCdata; + CRCdata >>= 8; + *table++ = (uint8_t) CRCdata; + CRCdata >>= 8; + *table++ = (uint8_t) CRCdata; + CRCdata >>= 8; + *table++ = (uint8_t) CRCdata; + } + + return 1; +} + +/* This function calculates the CRC from a data buffer + * + * Arguments: + * uint8_t *data: A pointer to the data that will be CRC'd. + * uint32_t bytes: The length of the data buffer. + * uint8_t index: The index of the CRC table to be used. + */ +uint8_t tmc_CRC8(uint8_t *data, uint32_t bytes, uint8_t index) +{ + uint8_t result = 0; + uint8_t *table; + + if(index >= CRC_TABLE_COUNT) + return 0; + + table = &CRCTables[index].table[0]; + + while(bytes--) + result = table[result ^ *data++]; + + return (CRCTables[index].isReflected)? flipByte(result) : result; +} + +uint8_t tmc_tableGetPolynomial(uint8_t index) +{ + if(index >= CRC_TABLE_COUNT) + return 0; + + return CRCTables[index].polynomial; +} + +bool tmc_tableIsReflected(uint8_t index) +{ + if(index >= CRC_TABLE_COUNT) + return false; + + return CRCTables[index].isReflected; +} + +// Helper functions +static uint8_t flipByte(uint8_t value) +{ + // swap odd and even bits + value = ((value >> 1) & 0x55) | ((value & 0x55) << 1); + // swap consecutive pairs + value = ((value >> 2) & 0x33) | ((value & 0x33) << 2); + // swap nibbles ... + value = ((value >> 4) & 0x0F) | ((value & 0x0F) << 4); + + return value; +} + +/* This helper function switches all bits within each byte. + * The byte order remains the same: + * [b31 b30 b29 b28 b27 b26 b25 b24 .. b7 b6 b5 b4 b3 b2 b1 b0] + * || + * \||/ + * \/ + * [b24 b25 b26 b27 b28 b29 b30 b31 .. b0 b1 b2 b3 b4 b5 b6 b7] + */ +static uint32_t flipBitsInBytes(uint32_t value) +{ + // swap odd and even bits + value = ((value >> 1) & 0x55555555) | ((value & 0x55555555) << 1); + // swap consecutive pairs + value = ((value >> 2) & 0x33333333) | ((value & 0x33333333) << 2); + // swap nibbles ... + value = ((value >> 4) & 0x0F0F0F0F) | ((value & 0x0F0F0F0F) << 4); + + return value; +} diff --git a/firmware/octoaxes/tmc/helpers/CRC.h b/firmware/octoaxes/tmc/helpers/CRC.h new file mode 100644 index 000000000..554383093 --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/CRC.h @@ -0,0 +1,23 @@ +/* + * CRC.h + * + * Created on: 04.12.2017 + * Author: LH + */ + +#ifndef TMC_HELPERS_CRC_H_ +#define TMC_HELPERS_CRC_H_ + + #include "Types.h" + + // Amount of CRC tables available + // Each table takes ~260 bytes (257 bytes, one bool and structure padding) + #define CRC_TABLE_COUNT 2 + + uint8_t tmc_fillCRC8Table(uint8_t polynomial, bool isReflected, uint8_t index); + uint8_t tmc_CRC8(uint8_t *data, uint32_t bytes, uint8_t index); + + uint8_t tmc_tableGetPolynomial(uint8_t index); + bool tmc_tableIsReflected(uint8_t index); + +#endif /* TMC_HELPERS_CRC_H_ */ diff --git a/firmware/octoaxes/tmc/helpers/Config.h b/firmware/octoaxes/tmc/helpers/Config.h new file mode 100644 index 000000000..8de24f0a6 --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/Config.h @@ -0,0 +1,39 @@ +/* + * Config.h + * + * Created on: 13.06.2018 + * Author: LK + */ + +#ifndef TMC_HELPERS_CONFIG_H_ +#define TMC_HELPERS_CONFIG_H_ + +#include "Constants.h" +#include "Types.h" + +// Callback functions have IC-dependent parameters +// To store the function pointers we use this dummy type, which is never +// called without casting it to the IC-specific type first. +// (Casting between function pointers is allowed by the C standard) +typedef void (*tmc_callback_config)(void); + +// States of a configuration +typedef enum { + CONFIG_READY, + CONFIG_RESET, + CONFIG_RESTORE +} ConfigState; + +// structure for configuration mechanism +typedef struct +{ + ConfigState state; + uint8_t configIndex; + int32_t shadowRegister[TMC_REGISTER_COUNT]; + uint8_t (*reset) (void); + uint8_t (*restore) (void); + tmc_callback_config callback; + uint8_t channel; +} ConfigurationTypeDef; + +#endif /* TMC_HELPERS_CONFIG_H_ */ diff --git a/firmware/octoaxes/tmc/helpers/Constants.h b/firmware/octoaxes/tmc/helpers/Constants.h new file mode 100644 index 000000000..b540a0656 --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/Constants.h @@ -0,0 +1,22 @@ +/* + * Constants.h + * + * Created on: 16.05.2018 + * Author: LK + */ + +#ifndef TMC_HELPERS_CONSTANTS_H_ +#define TMC_HELPERS_CONSTANTS_H_ + +#define TMC_WRITE_BIT 0x80 + +#define TMC_ADDRESS_MASK 0x7F + +#define TMC_DEFAULT_MOTOR 0 + +//#define TMC_DIRECTION_RIGHT TRUE +//#define TMC_DIRECTION_LEFT FALSE + +#define TMC_REGISTER_COUNT 128 // Default register count + +#endif /* TMC_HELPERS_CONSTANTS_H_ */ diff --git a/firmware/octoaxes/tmc/helpers/Functions.c b/firmware/octoaxes/tmc/helpers/Functions.c new file mode 100644 index 000000000..e2d224f8d --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/Functions.c @@ -0,0 +1,170 @@ +/* + * Functions.c + * + * Created on: 23.07.2018 + * Author: ed + */ +#include "Functions.h" + +int32_t tmc_limitInt(int32_t value, int32_t min, int32_t max) +{ + if (value > max) + return max; + else if (value < min) + return min; + else + return value; +} + +int64_t tmc_limitS64(int64_t value, int64_t min, int64_t max) +{ + if (value > max) + return max; + else if (value < min) + return min; + else + return value; +} + +/* lookup table for square root function */ +static const unsigned char sqrttable[256] = +{ + 0, 16, 22, 27, 32, 35, 39, 42, 45, 48, 50, 53, 55, 57, 59, 61, + 64, 65, 67, 69, 71, 73, 75, 76, 78, 80, 81, 83, 84, 86, 87, 89, + 90, 91, 93, 94, 96, 97, 98, 99, 101, 102, 103, 104, 106, 107, 108, 109, + 110, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, + 128, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 144, 145, 146, 147, 148, 149, 150, 150, 151, 152, 153, 154, 155, 155, + 156, 157, 158, 159, 160, 160, 161, 162, 163, 163, 164, 165, 166, 167, 167, 168, + 169, 170, 170, 171, 172, 173, 173, 174, 175, 176, 176, 177, 178, 178, 179, 180, + 181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 191, + 192, 192, 193, 193, 194, 195, 195, 196, 197, 197, 198, 199, 199, 200, 201, 201, + 202, 203, 203, 204, 204, 205, 206, 206, 207, 208, 208, 209, 209, 210, 211, 211, + 212, 212, 213, 214, 214, 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 221, + 221, 222, 222, 223, 224, 224, 225, 225, 226, 226, 227, 227, 228, 229, 229, 230, + 230, 231, 231, 232, 232, 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, + 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 247, + 247, 248, 248, 249, 249, 250, 250, 251, 251, 252, 252, 253, 253, 254, 254, 255 +}; + +int32_t tmc_sqrti(int32_t x) +{ + int32_t xn; + + // Negative parameter? + if (x < 0) + return -1; + + if (x < 0x0100) + return (int) sqrttable[x] >> 4; + + if (x >= 0x00010000) + { + if (x >= 0x01000000) + { + if (x >= 0x10000000) + { + if (x >= 0x40000000) + { + // 0x40000000 <= x < 0x7FFFFFFF + xn = (int) sqrttable[x >> 24] << 8; + } + else + { + // 0x10000000 <= x < 0x40000000 + xn = (int) sqrttable[x >> 22] << 7; + } + } + else + { + if (x >= 0x04000000) + { + // 0x04000000 <= x < 0x10000000 + xn = (int) sqrttable[x >> 20] << 6; + } + else + { + // 0x01000000 <= x < 0x04000000 + xn = (int) sqrttable[x >> 18] << 5; + } + } + + // Two steps of the babylonian method + xn = (xn + 1 + (x / xn)) >> 1; + xn = (xn + 1 + (x / xn)) >> 1; + } + else + { + if (x >= 0x00100000) + { + if (x >= 0x00400000) + { + // 0x00400000 <= x < 0x01000000 + xn = (int) sqrttable[x >> 16] << 4; + } + else + { + // 0x00100000 <= x < 0x00400000 + xn = (int) sqrttable[x >> 14] << 3; + } + } + else + { + if (x >= 0x00040000) + { + // 0x00040000 <= x < 0x00100000 + xn = (int) sqrttable[x >> 12] << 2; + } + else + { + // 0x00010000 <= x < 0x00040000 + xn = (int) sqrttable[x >> 10] << 1; + } + } + + // One step of the babylonian method + xn = (xn + 1 + (x / xn)) >> 1; + } + } + else + { + if (x >= 0x1000) + { + if (x >= 0x4000) + { + // 0x4000 <= x < 0x00010000 + xn = (int) (sqrttable[x >> 8] ) + 1; + } + else + { + // 0x1000 <= x < 0x4000 + xn = (int) (sqrttable[x >> 6] >> 1) + 1; + } + } + else + { + if (x >= 0x0400) + { + // 0x0400 <= x < 0x1000 + xn = (int) (sqrttable[x >> 4] >> 2) + 1; + } + else + { + // 0x0100 <= x < 0x0400 + xn = (int) (sqrttable[x >> 2] >> 3) + 1; + } + } + } + + // Make sure that our result is floored + if ((xn * xn) > x) + xn--; + + return xn; +} + +int32_t tmc_filterPT1(int64_t *akku, int32_t newValue, int32_t lastValue, uint8_t actualFilter, uint8_t maxFilter) +{ + *akku += (newValue-lastValue) << (maxFilter-actualFilter); + return *akku >> maxFilter; +} diff --git a/firmware/octoaxes/tmc/helpers/Functions.h b/firmware/octoaxes/tmc/helpers/Functions.h new file mode 100644 index 000000000..f5e871da7 --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/Functions.h @@ -0,0 +1,18 @@ +/* + * Functions.h + * + * Created on: 23.07.2018 + * Author: ed + */ + +#ifndef TMC_FUNCTIONS_H_ +#define TMC_FUNCTIONS_H_ + +#include "API_Header.h" + +int32_t tmc_limitInt(int32_t value, int32_t min, int32_t max); +int64_t tmc_limitS64(int64_t value, int64_t min, int64_t max); +int32_t tmc_sqrti(int32_t x); +int32_t tmc_filterPT1(int64_t *akku, int32_t newValue, int32_t lastValue, uint8_t actualFilter, uint8_t maxFilter); + +#endif /* TMC_FUNCTIONS_H_ */ diff --git a/firmware/octoaxes/tmc/helpers/Macros.h b/firmware/octoaxes/tmc/helpers/Macros.h new file mode 100644 index 000000000..b89ba92fe --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/Macros.h @@ -0,0 +1,55 @@ +/* + * Macros.h + * + * Created on: 04.01.2018 + * Author: LH + */ + +#ifndef TMC_MACROS_H_ +#define TMC_MACROS_H_ + +/* Cast a n bit signed int to a 32 bit signed int + * This is done by checking the MSB of the signed int (Bit n). + * If it is 1, the value is negative and the Bits 32 to n+1 are set to 1 + * If it is 0, the value remains unchanged + */ +#define CAST_Sn_TO_S32(value, n) ((value) | (((value) & ((uint32_t)1<<((n)-1)))? ~(((uint32_t)1<<(n))-1) : 0 )) + +// Min/Max macros +#ifndef MIN + #define MIN(a,b) (((a)<(b)) ? (a) : (b)) +#endif +#ifndef MAX + #define MAX(a,b) (((a)>(b)) ? (a) : (b)) +#endif + +// Static Array length +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +// Generic mask/shift macros +#define FIELD_GET(data, mask, shift) \ + (((data) & (mask)) >> (shift)) +#define FIELD_SET(data, mask, shift, value) \ + (((data) & (~(mask))) | (((value) << (shift)) & (mask))) + +// Register read/write/update macros using Mask/Shift: +#define FIELD_READ(read, motor, address, mask, shift) \ + FIELD_GET(read(motor, address), mask, shift) +#define FIELD_WRITE(write, motor, address, mask, shift, value) \ + (write(motor, address, ((value)<<(shift)) & (mask))) +#define FIELD_UPDATE(read, write, motor, address, mask, shift, value) \ + (write(motor, address, FIELD_SET(read(motor, address), mask, shift, value))) + +// Macro to surpress unused parameter warnings +#ifndef UNUSED + #define UNUSED(x) (void)(x) +#endif + +// Memory access helpers +// Force the compiler to access a location exactly once +#define ACCESS_ONCE(x) *((volatile typeof(x) *) (&x)) + +// Macro to remove write bit for shadow register array access +#define TMC_ADDRESS(x) ((x) & (TMC_ADDRESS_MASK)) + +#endif /* TMC_MACROS_H_ */ diff --git a/firmware/octoaxes/tmc/helpers/RegisterAccess.h b/firmware/octoaxes/tmc/helpers/RegisterAccess.h new file mode 100644 index 000000000..ab862cf26 --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/RegisterAccess.h @@ -0,0 +1,83 @@ +/* + * RegisterAccess.h + * + * Created on: 12.07.2017 + * Author: LK + * + * The permission system aims to allow a general-purpose implementation for + * all common hardware register usages. This includes: + * - Trivial Cases: Read, Write, Read & Write + * + * - Read & Write accesses that route to different values/functions of a chip. + * (e.g. serial communication, where read/write corresponds to RX/TX) + * - Read to clear, write to clear. This does not directly affect the access, + * but can be used to implement a software shadow register for flags + * (ORing the read value into a shadow register instead of overwriting). + * - Registers with default values that are not known (e.g. Factory configuration + * values that should not be overwritten by default). + */ + +#ifndef TMC_HELPERS_REGISTERACCESS_H +#define TMC_HELPERS_REGISTERACCESS_H + +// Register access bits +/* Lower nibble is used for read/write, higher nibble is used for + * special case registers. This makes it easy to identify the read/write + * part of the permissions in a hexadecimal permission number. + * The dirty bit will only ever be set at runtime, so we keep the easily + * readable lower nibble. + */ +#define TMC_ACCESS_NONE 0x00 + +#define TMC_ACCESS_READ 0x01 +#define TMC_ACCESS_WRITE 0x02 + // 0x04 is currently unused +#define TMC_ACCESS_DIRTY 0x08 // Register has been written since reset -> shadow register is valid for restore + +// Special Register bits +#define TMC_ACCESS_RW_SPECIAL 0x10 // Read and write are independent - different values and/or different functions +#define TMC_ACCESS_FLAGS 0x20 // Register has read or write to clear flags. +#define TMC_ACCESS_HW_PRESET 0x40 // Register has hardware presets (e.g. Factory calibrations) - do not write a default value + // 0x80 is currently unused + +// Permission combinations +#define TMC_ACCESS_RW (TMC_ACCESS_READ | TMC_ACCESS_WRITE) // 0x03 - Read and write +#define TMC_ACCESS_RW_SEPARATE (TMC_ACCESS_RW | TMC_ACCESS_RW_SPECIAL) // 0x13 - Read and write, with separate values/functions +#define TMC_ACCESS_R_FLAGS (TMC_ACCESS_READ | TMC_ACCESS_FLAGS) // 0x21 - Read, has flags (read to clear) +#define TMC_ACCESS_RW_FLAGS (TMC_ACCESS_RW | TMC_ACCESS_FLAGS) // 0x23 - Read and write, has flags (read or write to clear) +#define TMC_ACCESS_W_PRESET (TMC_ACCESS_WRITE | TMC_ACCESS_HW_PRESET) // 0x42 - Write, has hardware preset - skipped in reset routine +#define TMC_ACCESS_RW_PRESET (TMC_ACCESS_RW | TMC_ACCESS_HW_PRESET) // 0x43 - Read and write, has hardware presets - skipped in reset routine + +// Helper macros +#define TMC_IS_READABLE(x) ((x) & TMC_ACCESS_READ) +#define TMC_IS_WRITABLE(x) ((x) & TMC_ACCESS_WRITE) +#define TMC_IS_DIRTY(x) ((x) & TMC_ACCESS_DIRTY) +#define TMC_IS_PRESET(x) ((x) & TMC_ACCESS_HW_PRESET) +#define TMC_IS_RESETTABLE(x) (((x) & (TMC_ACCESS_W_PRESET)) == TMC_ACCESS_WRITE) // Write bit set, Hardware preset bit not set +#define TMC_IS_RESTORABLE(x) (((x) & TMC_ACCESS_WRITE) && (!(x & TMC_ACCESS_HW_PRESET) || (x & TMC_ACCESS_DIRTY))) // Write bit set, if it's a hardware preset register, it needs to be dirty + +// Struct for listing registers that have constant contents which we cannot +// obtain by reading them due to the register not being read-back. +typedef struct +{ + uint8_t address; + uint32_t value; +} TMCRegisterConstant; + +// Helper define: +// Most register permission arrays are initialized with 128 values. +// In those fields its quite hard to have an easy overview of available +// registers. For that, ____ is defined to 0, since 4 underscores are +// very easy to distinguish from the 2-digit hexadecimal values. +// This way, the used registers (permission != ACCESS_NONE) are easily spotted +// amongst unused (permission == ACCESS_NONE) registers. +#define ____ 0x00 + +// Helper define: +// Default reset values are not used if the corresponding register has a +// hardware preset. Since this is not directly visible in the default +// register reset values array, N_A is used as an indicator for a preset +// value, where any value will be ignored anyways (N_A: not available). +#define N_A 0 + +#endif /* TMC_HELPERS_REGISTERACCESS_H */ diff --git a/firmware/octoaxes/tmc/helpers/Types.h b/firmware/octoaxes/tmc/helpers/Types.h new file mode 100644 index 000000000..d7f404c39 --- /dev/null +++ b/firmware/octoaxes/tmc/helpers/Types.h @@ -0,0 +1,100 @@ +/* + * Types.h + * + * Created on: 29.09.2016 + * Author: ed + */ + +// Turn off the integer typedefs by uncommenting the following two defines. +// If your IDE (e.g. Arduino IDE) already defines these types, deactivating +// these typedefs will fix errors and/or warnings. +//#define TMC_SKIP_UINT_TYPEDEFS // Disable u8, u16, u32, uint8, uint16 and uint32 typedefs +//#define TMC_SKIP_INT_TYPEDEFS // Disable s8, s16, s32, int8, int16 and int32 typedefs + +#ifndef TMC_TYPES_H_ +#define TMC_TYPES_H_ + +#include +#include +#include + +#ifndef TMC_TYPES_INTEGERS +#define TMC_TYPES_INTEGERS + +// todo: change to standard ISO C99 types (ED) + +// www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf +// ISO C99: 7.18 Integer types 8, 16, 32, or 64 bits +// intN_t = two’s complement signed integer type with width N, no padding bits. +// uintN_t = an unsigned integer type with width N. +// floatN_t = N bit IEE 754 float. +// INT8_MIN, INT8_MAX, INT16_MIN, INT16_MAX, INT32_MIN, INT32_MAX, .... UINT32_MAX + +typedef float float32_t; +typedef double float64_t; + +#ifndef TMC_TYPES_INTEGERS_UNSIGNED +#define TMC_TYPES_INTEGERS_UNSIGNED + +#ifndef TMC_SKIP_UINT_TYPEDEFS +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +#endif /* TMC_SKIP_UINT_TYPEDEFS */ + +#define u8_MAX (uint8_t) 255 +#define u10_MAX (uint16_t) 1023 +#define u12_MAX (uint16_t) 4095 +#define u15_MAX (uint16_t) 32767 +#define u16_MAX (uint16_t) 65535 +#define u18_MAX (uint32_t) 262143uL +#define u20_MAX (uint32_t) 1048575uL +#define u22_MAX (uint32_t) 4194303uL +#define u24_MAX (uint32_t) 16777215uL +#define u32_MAX (uint32_t) 4294967295uL + +#endif /* TMC_TYPES_INTEGERS_UNSIGNED */ + +#ifndef TMC_TYPES_INTEGERS_SIGNED +#define TMC_TYPES_INTEGERS_SIGNED + +#ifndef TMC_SKIP_INT_TYPEDEFS +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; + +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +#endif /* TMC_SKIP_INT_TYPEDEFS */ + +#define s8_MAX (int8_t) 127 +#define s8_MIN (int8_t) -128 +#define s16_MAX (int16_t) 32767 +#define s16_MIN (int16_t) -32768 +#define s24_MAX (int32_t) 8388607 +#define s24_MIN (int32_t) -8388608 +#define s32_MAX (int32_t) 2147483647 +#define s32_MIN (int32_t) -2147483648 + +#endif /* TMC_TYPES_INTEGERS_SIGNED */ + +#endif /* TMC_TYPES_INTEGERS */ + +#ifndef TMC_TYPES_NULL +#define TMC_TYPES_NULL + +#ifndef NULL +#define NULL ((void *) 0) +#endif /* NULL */ + +#define FALSE false +#define TRUE true + +#endif /* TMC_TYPES_NULL */ + +#endif /* TMC_TYPES_H_ */ diff --git a/firmware/octoaxes/tmc/ic/TMC2240/TMC2240.cpp b/firmware/octoaxes/tmc/ic/TMC2240/TMC2240.cpp new file mode 100644 index 000000000..39c55bb63 --- /dev/null +++ b/firmware/octoaxes/tmc/ic/TMC2240/TMC2240.cpp @@ -0,0 +1,205 @@ +/* + * TMC2240.cpp + * + * TMC2240 stepper driver implementation for Octoaxes project. + * SPI communication only (through TMC4361A Cover interface). + * + * Based on official TMC-API, adapted for multi-IC support. + * Original: Copyright © 2017 TRINAMIC / 2024 Analog Devices Inc. + * + * Created: 2026-03-16 + */ + +#include "TMC2240.h" + +// ============================================================================ +// Cache Implementation +// ============================================================================ + +#if TMC2240_CACHE == 0 +static inline bool tmc2240_cache(uint16_t icID, TMC2240CacheOp operation, uint8_t address, uint32_t *value) +{ + (void)icID; + (void)address; + (void)operation; + (void)value; + return false; +} +#else +#if TMC2240_ENABLE_TMC_CACHE == 1 + +uint8_t tmc2240_dirtyBits[TMC2240_IC_CACHE_COUNT][TMC2240_REGISTER_COUNT / 8] = {0}; +int32_t tmc2240_shadowRegister[TMC2240_IC_CACHE_COUNT][TMC2240_REGISTER_COUNT]; + +void tmc2240_setDirtyBit(uint16_t icID, uint8_t index, bool value) +{ + if (index >= TMC2240_REGISTER_COUNT || icID >= TMC2240_IC_CACHE_COUNT) + return; + + uint8_t *tmp = &tmc2240_dirtyBits[icID][index / 8]; + uint8_t shift = (index % 8); + uint8_t mask = 1 << shift; + *tmp = (((*tmp) & (~mask)) | ((value ? 1 : 0) << shift)); +} + +bool tmc2240_getDirtyBit(uint16_t icID, uint8_t index) +{ + if (index >= TMC2240_REGISTER_COUNT || icID >= TMC2240_IC_CACHE_COUNT) + return false; + + uint8_t *tmp = &tmc2240_dirtyBits[icID][index / 8]; + uint8_t shift = (index % 8); + return ((*tmp) >> shift) & 1; +} + +bool tmc2240_cache(uint16_t icID, TMC2240CacheOp operation, uint8_t address, uint32_t *value) +{ + if (operation == TMC2240_CACHE_READ) + { + if (icID >= TMC2240_IC_CACHE_COUNT) + return false; + if (TMC2240_IS_READABLE(tmc2240_registerAccess[address])) + return false; + *value = tmc2240_shadowRegister[icID][address]; + return true; + } + else if (operation == TMC2240_CACHE_WRITE || operation == TMC2240_CACHE_FILL_DEFAULT) + { + if (icID >= TMC2240_IC_CACHE_COUNT) + return false; + tmc2240_shadowRegister[icID][address] = *value; + if (operation == TMC2240_CACHE_WRITE) + { + tmc2240_setDirtyBit(icID, address, true); + } + return true; + } + return false; +} + +void tmc2240_initCache(void) +{ + if (ARRAY_SIZE(tmc2240_RegisterConstants) == 0) + return; + + size_t i, j, id; + + for (i = 0, j = 0; i < TMC2240_REGISTER_COUNT; i++) + { + if (tmc2240_registerAccess[i] != TMC2240_ACCESS_W_PRESET) + continue; + + while (j < ARRAY_SIZE(tmc2240_RegisterConstants) && + (tmc2240_RegisterConstants[j].address < i)) + j++; + + if (j == ARRAY_SIZE(tmc2240_RegisterConstants)) + break; + + if (tmc2240_RegisterConstants[j].address == i) + { + for (id = 0; id < TMC2240_IC_CACHE_COUNT; id++) + { + uint32_t temp = tmc2240_RegisterConstants[j].value; + tmc2240_cache(id, TMC2240_CACHE_FILL_DEFAULT, i, &temp); + } + } + } +} + +#else +// User must implement their own cache +extern bool tmc2240_cache(uint16_t icID, TMC2240CacheOp operation, uint8_t address, uint32_t *value); +#endif +#endif + +// ============================================================================ +// SPI Read/Write (through TMC4361A Cover interface) +// ============================================================================ + +static int32_t readRegisterSPI(uint16_t icID, uint8_t address); +static void writeRegisterSPI(uint16_t icID, uint8_t address, int32_t value); + +int32_t tmc2240_readRegister(uint16_t icID, uint8_t address) +{ + uint32_t value; + + // Read from cache for write-only registers + if (tmc2240_cache(icID, TMC2240_CACHE_READ, address, &value)) + return value; + + return readRegisterSPI(icID, address); +} + +void tmc2240_writeRegister(uint16_t icID, uint8_t address, int32_t value) +{ + writeRegisterSPI(icID, address, value); + + // Cache the registers with write-only access + tmc2240_cache(icID, TMC2240_CACHE_WRITE, address, (uint32_t *)&value); +} + +static int32_t readRegisterSPI(uint16_t icID, uint8_t address) +{ + uint8_t data[5] = {0}; + + // Clear write bit + data[0] = address & TMC2240_ADDRESS_MASK; + + // First SPI transfer: send read request + tmc2240_readWriteSPI(icID, &data[0], sizeof(data)); + + // Rewrite address and clear write bit + data[0] = address & TMC2240_ADDRESS_MASK; + + // Second SPI transfer: receive read reply + tmc2240_readWriteSPI(icID, &data[0], sizeof(data)); + + return ((int32_t)data[1] << 24) | ((int32_t)data[2] << 16) | + ((int32_t)data[3] << 8) | ((int32_t)data[4]); +} + +static void writeRegisterSPI(uint16_t icID, uint8_t address, int32_t value) +{ + uint8_t data[5] = {0}; + + data[0] = address | TMC2240_WRITE_BIT; + data[1] = 0xFF & (value >> 24); + data[2] = 0xFF & (value >> 16); + data[3] = 0xFF & (value >> 8); + data[4] = 0xFF & (value >> 0); + + // Send write request via HAL callback (routes through TMC4361A Cover) + tmc2240_readWriteSPI(icID, &data[0], sizeof(data)); +} + +// ============================================================================ +// High-Level Configuration API +// ============================================================================ + +void tmc2240_setRunCurrent(uint16_t icID, uint8_t irun) +{ + if (irun > 31) irun = 31; + tmc2240_fieldWrite(icID, TMC2240_IRUN_FIELD, irun); +} + +void tmc2240_setHoldCurrent(uint16_t icID, uint8_t ihold) +{ + if (ihold > 31) ihold = 31; + tmc2240_fieldWrite(icID, TMC2240_IHOLD_FIELD, ihold); +} + +void tmc2240_enableDriver(uint16_t icID, bool enable) +{ + // TOFF=0 disables driver, TOFF>0 enables + if (enable) { + uint32_t chopconf = tmc2240_readRegister(icID, TMC2240_CHOPCONF); + uint8_t toff = (chopconf & TMC2240_TOFF_MASK) >> TMC2240_TOFF_SHIFT; + if (toff == 0) { + // Restore default TOFF=3 + tmc2240_fieldWrite(icID, TMC2240_TOFF_FIELD, 3); + } + } else { + tmc2240_fieldWrite(icID, TMC2240_TOFF_FIELD, 0); + } +} diff --git a/firmware/octoaxes/tmc/ic/TMC2240/TMC2240.h b/firmware/octoaxes/tmc/ic/TMC2240/TMC2240.h new file mode 100644 index 000000000..7261a787f --- /dev/null +++ b/firmware/octoaxes/tmc/ic/TMC2240/TMC2240.h @@ -0,0 +1,225 @@ +/* + * TMC2240.h + * + * TMC2240 stepper driver for Octoaxes project. + * Communicates through TMC4361A Cover interface (40-bit SPI). + * + * Based on official TMC-API, adapted for multi-IC support. + * Original: Copyright © 2017 TRINAMIC / 2024 Analog Devices Inc. + * + * Created: 2026-03-16 + */ + +#ifndef TMC_IC_TMC2240_H_ +#define TMC_IC_TMC2240_H_ + +#include +#include +#include +#include "TMC2240_HW_Abstraction.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================ +// API Configuration +// ============================================================================ + +#ifndef TMC2240_CACHE +#define TMC2240_CACHE 1 +#endif + +#ifndef TMC2240_ENABLE_TMC_CACHE +#define TMC2240_ENABLE_TMC_CACHE 1 +#endif + +// Number of ICs (same as TMC4361A, paired 1:1) +#ifndef TMC2240_IC_CACHE_COUNT +#define TMC2240_IC_CACHE_COUNT 7 +#endif + +// ============================================================================ +// Bus Type +// ============================================================================ + +typedef enum { + TMC2240_BUS_SPI, + TMC2240_BUS_UART, +} TMC2240BusType; + +// ============================================================================ +// HAL Callbacks (implemented in MotorControl.cpp) +// ============================================================================ + +extern void tmc2240_readWriteSPI(uint16_t icID, uint8_t *data, size_t dataLength); +extern TMC2240BusType tmc2240_getBusType(uint16_t icID); + +// ============================================================================ +// Core Register API +// ============================================================================ + +int32_t tmc2240_readRegister(uint16_t icID, uint8_t address); +void tmc2240_writeRegister(uint16_t icID, uint8_t address, int32_t value); + +// ============================================================================ +// Field-Level Operations +// ============================================================================ + +// RegisterField type — shared with TMC4361A_HW_Abstraction.h macros +#ifndef REGISTER_FIELD_DEFINED +#define REGISTER_FIELD_DEFINED +typedef struct { + uint32_t mask; + uint8_t shift; + uint8_t address; + bool isSigned; +} RegisterField; +#endif + +static inline uint32_t tmc2240_fieldExtract(uint32_t data, RegisterField field) +{ + uint32_t value = (data & field.mask) >> field.shift; + if (field.isSigned) { + uint32_t baseMask = field.mask >> field.shift; + uint32_t signMask = baseMask & (~baseMask >> 1); + value = (value ^ signMask) - signMask; + } + return value; +} + +static inline uint32_t tmc2240_fieldRead(uint16_t icID, RegisterField field) +{ + uint32_t value = tmc2240_readRegister(icID, field.address); + return tmc2240_fieldExtract(value, field); +} + +static inline uint32_t tmc2240_fieldUpdate(uint32_t data, RegisterField field, uint32_t value) +{ + return (data & (~field.mask)) | ((value << field.shift) & field.mask); +} + +static inline void tmc2240_fieldWrite(uint16_t icID, RegisterField field, uint32_t value) +{ + uint32_t regValue = tmc2240_readRegister(icID, field.address); + regValue = tmc2240_fieldUpdate(regValue, field, value); + tmc2240_writeRegister(icID, field.address, regValue); +} + +// ============================================================================ +// High-Level Configuration API +// ============================================================================ + +/** + * @brief Set run current (IRUN field, 0-31) + */ +void tmc2240_setRunCurrent(uint16_t icID, uint8_t irun); + +/** + * @brief Set hold current (IHOLD field, 0-31) + */ +void tmc2240_setHoldCurrent(uint16_t icID, uint8_t ihold); + +/** + * @brief Enable/disable driver (via CHOPCONF.TOFF) + */ +void tmc2240_enableDriver(uint16_t icID, bool enable); + +// ============================================================================ +// Cache Implementation +// ============================================================================ + +#if TMC2240_CACHE == 1 +#if TMC2240_ENABLE_TMC_CACHE == 1 + +typedef enum { + TMC2240_CACHE_READ, + TMC2240_CACHE_WRITE, + TMC2240_CACHE_FILL_DEFAULT, +} TMC2240CacheOp; + +typedef struct { + uint8_t address; + uint32_t value; +} TMC2240RegisterConstants; + +#define TMC2240_ACCESS_DIRTY 0x08 +#define TMC2240_ACCESS_READ 0x01 +#define TMC2240_ACCESS_W_PRESET 0x42 +#define TMC2240_IS_READABLE(x) ((x) & TMC2240_ACCESS_READ) + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#endif + +#ifndef ____ +#define ____ 0x00 +#endif +#ifndef N_A +#define N_A 0 +#endif + +// Default register values +#define R2240_00 0x00002108 // GCONF +#define R2240_0A 0x00000020 // DRV_CONF +#define R2240_10 0x00070A03 // IHOLD_IRUN +#define R2240_11 0x0000000A // TPOWERDOWN +#define R2240_6C 0x14410153 // CHOPCONF +#define R2240_70 ((int32_t)0xC44C001E) // PWMCONF + +// Register access permissions +static const uint8_t tmc2240_registerAccess[TMC2240_REGISTER_COUNT] = +{ + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x03, 0x23, 0x01, 0x03, 0x03, ____, ____, ____, ____, ____, 0x03, 0x03, ____, ____, ____, ____, // 0x00 + 0x03, 0x03, 0x01, 0x03, 0x03, 0x03, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, // 0x10 + ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 0x03, ____, ____, // 0x20 + ____, ____, ____, ____, ____, ____, ____, ____, 0x03, 0x03, 0x03, 0x23, 0x01, ____, ____, ____, // 0x30 + ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, // 0x40 + 0x01, 0x01, 0x03, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, // 0x50 + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x01, 0x01, 0x03, 0x03, ____, 0x01, // 0x60 + 0x03, 0x01, 0x01, ____, 0x03, 0x01, 0x01, ____, ____, ____, ____, ____, ____, ____, ____, ____ // 0x70 +}; + +static const int32_t tmc2240_sampleRegisterPreset[TMC2240_REGISTER_COUNT] = +{ + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F + R2240_00, 0, 0, 0, 0, 0, 0, 0, 0, 0, R2240_0A, 0, 0, 0, 0, 0, // 0x00 + R2240_10, R2240_11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x30 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x50 + N_A, N_A, N_A, N_A, N_A, N_A, N_A, N_A, N_A, N_A, 0, 0, R2240_6C, 0, 0, 0, // 0x60 + R2240_70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x70 +}; + +static const TMC2240RegisterConstants tmc2240_RegisterConstants[] = +{ + { 0x60, 0xAAAAB554 }, // MSLUT[0] + { 0x61, 0x4A9554AA }, // MSLUT[1] + { 0x62, 0x24492929 }, // MSLUT[2] + { 0x63, 0x10104222 }, // MSLUT[3] + { 0x64, 0xFBFFFFFF }, // MSLUT[4] + { 0x65, 0xB5BB777D }, // MSLUT[5] + { 0x66, 0x49295556 }, // MSLUT[6] + { 0x67, 0x00404222 }, // MSLUT[7] + { 0x68, 0xFFFF8056 }, // MSLUTSEL + { 0x69, 0x00F70000 }, // MSLUTSTART +}; + +extern uint8_t tmc2240_dirtyBits[TMC2240_IC_CACHE_COUNT][TMC2240_REGISTER_COUNT / 8]; +extern int32_t tmc2240_shadowRegister[TMC2240_IC_CACHE_COUNT][TMC2240_REGISTER_COUNT]; +bool tmc2240_cache(uint16_t icID, TMC2240CacheOp operation, uint8_t address, uint32_t *value); +void tmc2240_initCache(void); +void tmc2240_setDirtyBit(uint16_t icID, uint8_t index, bool value); +bool tmc2240_getDirtyBit(uint16_t icID, uint8_t index); + +#endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* TMC_IC_TMC2240_H_ */ diff --git a/firmware/octoaxes/tmc/ic/TMC2240/TMC2240_HW_Abstraction.h b/firmware/octoaxes/tmc/ic/TMC2240/TMC2240_HW_Abstraction.h new file mode 100644 index 000000000..6b120294f --- /dev/null +++ b/firmware/octoaxes/tmc/ic/TMC2240/TMC2240_HW_Abstraction.h @@ -0,0 +1,567 @@ +/******************************************************************************* +* Copyright © 2019 TRINAMIC Motion Control GmbH & Co. KG +* (now owned by Analog Devices Inc.), +* +* Copyright © 2024 Analog Devices Inc. All Rights Reserved. +* This software is proprietary to Analog Devices, Inc. and its licensors. +*******************************************************************************/ + + +#ifndef TMC_IC_TMC2240_HW_ABSTRACTION_H_ +#define TMC_IC_TMC2240_HW_ABSTRACTION_H_ + +// Constants +#define TMC2240_REGISTER_COUNT 128 +#define TMC2240_MOTORS 1 +#define TMC2240_WRITE_BIT 0x80 +#define TMC2240_ADDRESS_MASK 0x7F +#define TMC2240_MAX_VELOCITY 8388096 +#define TMC2240_MAX_ACCELERATION (uint16_t) 65535 + +// ramp modes (Register TMC2240_RAMPMODE) +#define TMC2240_MODE_POSITION 0 +#define TMC2240_MODE_VELPOS 1 +#define TMC2240_MODE_VELNEG 2 +#define TMC2240_MODE_HOLD 3 + +// limit switch mode bits (Register TMC2240_SWMODE) +#define TMC2240_SW_STOPL_ENABLE 0x0001 +#define TMC2240_SW_STOPR_ENABLE 0x0002 +#define TMC2240_SW_STOPL_POLARITY 0x0004 +#define TMC2240_SW_STOPR_POLARITY 0x0008 +#define TMC2240_SW_SWAP_LR 0x0010 +#define TMC2240_SW_LATCH_L_ACT 0x0020 +#define TMC2240_SW_LATCH_L_INACT 0x0040 +#define TMC2240_SW_LATCH_R_ACT 0x0080 +#define TMC2240_SW_LATCH_R_INACT 0x0100 +#define TMC2240_SW_LATCH_ENC 0x0200 +#define TMC2240_SW_SG_STOP 0x0400 +#define TMC2240_SW_SOFTSTOP 0x0800 + +// Status bits (Register TMC2240_RAMPSTAT) +#define TMC2240_RS_STOPL 0x0001 +#define TMC2240_RS_STOPR 0x0002 +#define TMC2240_RS_LATCHL 0x0004 +#define TMC2240_RS_LATCHR 0x0008 +#define TMC2240_RS_EV_STOPL 0x0010 +#define TMC2240_RS_EV_STOPR 0x0020 +#define TMC2240_RS_EV_STOP_SG 0x0040 +#define TMC2240_RS_EV_POSREACHED 0x0080 +#define TMC2240_RS_VELREACHED 0x0100 +#define TMC2240_RS_POSREACHED 0x0200 +#define TMC2240_RS_VZERO 0x0400 +#define TMC2240_RS_ZEROWAIT 0x0800 +#define TMC2240_RS_SECONDMOVE 0x1000 +#define TMC2240_RS_SG 0x2000 + +// Encoderbits (Register TMC2240_ENCMODE) +#define TMC2240_EM_DECIMAL 0x0400 +#define TMC2240_EM_LATCH_XACT 0x0200 +#define TMC2240_EM_CLR_XENC 0x0100 +#define TMC2240_EM_NEG_EDGE 0x0080 +#define TMC2240_EM_POS_EDGE 0x0040 +#define TMC2240_EM_CLR_ONCE 0x0020 +#define TMC2240_EM_CLR_CONT 0x0010 +#define TMC2240_EM_IGNORE_AB 0x0008 +#define TMC2240_EM_POL_N 0x0004 +#define TMC2240_EM_POL_B 0x0002 +#define TMC2240_EM_POL_A 0x0001 + + +// Registers +#define TMC2240_GCONF 0x00 +#define TMC2240_GSTAT 0x01 +#define TMC2240_IFCNT 0x02 +#define TMC2240_SLAVECONF 0x03 +#define TMC2240_IOIN 0x04 +#define TMC2240_DRV_CONF 0x0A +#define TMC2240_GLOBAL_SCALER 0x0B + +#define TMC2240_IHOLD_IRUN 0x10 +#define TMC2240_TPOWERDOWN 0x11 +#define TMC2240_TSTEP 0x12 +#define TMC2240_TPWMTHRS 0x13 +#define TMC2240_TCOOLTHRS 0x14 +#define TMC2240_THIGH 0x15 + +#define TMC2240_DIRECT_MODE 0x2D + +#define TMC2240_ENCMODE 0x38 +#define TMC2240_XENC 0x39 +#define TMC2240_ENC_CONST 0x3A +#define TMC2240_ENC_STATUS 0x3B +#define TMC2240_ENC_LATCH 0x3C + +#define TMC2240_ADC_VSUPPLY_AIN 0x50 +#define TMC2240_ADC_TEMP 0x51 +#define TMC2240_OTW_OV_VTH 0x52 + +#define TMC2240_MSLUT0 0x60 +#define TMC2240_MSLUT1 0x61 +#define TMC2240_MSLUT2 0x62 +#define TMC2240_MSLUT3 0x63 +#define TMC2240_MSLUT4 0x64 +#define TMC2240_MSLUT5 0x65 +#define TMC2240_MSLUT6 0x66 +#define TMC2240_MSLUT7 0x67 +#define TMC2240_MSLUTSEL 0x68 +#define TMC2240_MSLUTSTART 0x69 +#define TMC2240_MSCNT 0x6A +#define TMC2240_MSCURACT 0x6B +#define TMC2240_CHOPCONF 0x6C +#define TMC2240_COOLCONF 0x6D +#define TMC2240_DCCTRL 0x6E +#define TMC2240_DRVSTATUS 0x6F + +#define TMC2240_PWMCONF 0x70 +#define TMC2240_PWM_SCALE 0x71 +#define TMC2240_PWM_AUTO 0x72 +#define TMC2240_SG4_THRS 0x74 +#define TMC2240_SG4_RESULT 0x75 +#define TMC2240_SG4_IND 0x76 + + +// Register fields +#define TMC2240_FAST_STANDSTILL_MASK 0x00000002 +#define TMC2240_FAST_STANDSTILL_SHIFT 1 +#define TMC2240_FAST_STANDSTILL_FIELD ((RegisterField) {TMC2240_FAST_STANDSTILL_MASK, TMC2240_FAST_STANDSTILL_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_EN_PWM_MODE_MASK 0x00000004 +#define TMC2240_EN_PWM_MODE_SHIFT 2 +#define TMC2240_EN_PWM_MODE_FIELD ((RegisterField) {TMC2240_EN_PWM_MODE_MASK, TMC2240_EN_PWM_MODE_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_MULTISTEP_FILT_MASK 0x00000008 +#define TMC2240_MULTISTEP_FILT_SHIFT 3 +#define TMC2240_MULTISTEP_FILT_FIELD ((RegisterField) {TMC2240_MULTISTEP_FILT_MASK, TMC2240_MULTISTEP_FILT_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_SHAFT_MASK 0x00000010 +#define TMC2240_SHAFT_SHIFT 4 +#define TMC2240_SHAFT_FIELD ((RegisterField) {TMC2240_SHAFT_MASK, TMC2240_SHAFT_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_DIAG0_ERROR_MASK 0x00000020 +#define TMC2240_DIAG0_ERROR_SHIFT 5 +#define TMC2240_DIAG0_ERROR_FIELD ((RegisterField) {TMC2240_DIAG0_ERROR_MASK, TMC2240_DIAG0_ERROR_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_DIAG0_OTPW_MASK 0x00000040 +#define TMC2240_DIAG0_OTPW_SHIFT 6 +#define TMC2240_DIAG0_OTPW_FIELD ((RegisterField) {TMC2240_DIAG0_OTPW_MASK, TMC2240_DIAG0_OTPW_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_DIAG0_STALL_MASK 0x00000080 +#define TMC2240_DIAG0_STALL_SHIFT 7 +#define TMC2240_DIAG0_STALL_FIELD ((RegisterField) {TMC2240_DIAG0_STALL_MASK, TMC2240_DIAG0_STALL_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_DIAG1_STALL_MASK 0x00000100 +#define TMC2240_DIAG1_STALL_SHIFT 8 +#define TMC2240_DIAG1_STALL_FIELD ((RegisterField) {TMC2240_DIAG1_STALL_MASK, TMC2240_DIAG1_STALL_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_DIAG1_INDEX_MASK 0x00000200 +#define TMC2240_DIAG1_INDEX_SHIFT 9 +#define TMC2240_DIAG1_INDEX_FIELD ((RegisterField) {TMC2240_DIAG1_INDEX_MASK, TMC2240_DIAG1_INDEX_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_DIAG1_ONSTATE_MASK 0x00000400 +#define TMC2240_DIAG1_ONSTATE_SHIFT 10 +#define TMC2240_DIAG1_ONSTATE_FIELD ((RegisterField) {TMC2240_DIAG1_ONSTATE_MASK, TMC2240_DIAG1_ONSTATE_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_DIAG0_PUSHPULL_MASK 0x00001000 +#define TMC2240_DIAG0_PUSHPULL_SHIFT 12 +#define TMC2240_DIAG0_PUSHPULL_FIELD ((RegisterField) {TMC2240_DIAG0_PUSHPULL_MASK, TMC2240_DIAG0_PUSHPULL_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_DIAG1_PUSHPULL_MASK 0x00002000 +#define TMC2240_DIAG1_PUSHPULL_SHIFT 13 +#define TMC2240_DIAG1_PUSHPULL_FIELD ((RegisterField) {TMC2240_DIAG1_PUSHPULL_MASK, TMC2240_DIAG1_PUSHPULL_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_SMALL_HYSTERESIS_MASK 0x00004000 +#define TMC2240_SMALL_HYSTERESIS_SHIFT 14 +#define TMC2240_SMALL_HYSTERESIS_FIELD ((RegisterField) {TMC2240_SMALL_HYSTERESIS_MASK, TMC2240_SMALL_HYSTERESIS_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_STOP_ENABLE_MASK 0x00008000 +#define TMC2240_STOP_ENABLE_SHIFT 15 +#define TMC2240_STOP_ENABLE_FIELD ((RegisterField) {TMC2240_STOP_ENABLE_MASK, TMC2240_STOP_ENABLE_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_DIRECT_MODE_MASK 0x00010000 +#define TMC2240_DIRECT_MODE_SHIFT 16 +#define TMC2240_DIRECT_MODE_FIELD ((RegisterField) {TMC2240_DIRECT_MODE_MASK, TMC2240_DIRECT_MODE_SHIFT, TMC2240_GCONF, false}) +#define TMC2240_RESET_MASK 0x00000001 +#define TMC2240_RESET_SHIFT 0 +#define TMC2240_RESET_FIELD ((RegisterField) {TMC2240_RESET_MASK, TMC2240_RESET_SHIFT, TMC2240_GSTAT, false}) +#define TMC2240_DRV_ERR_MASK 0x00000002 +#define TMC2240_DRV_ERR_SHIFT 1 +#define TMC2240_DRV_ERR_FIELD ((RegisterField) {TMC2240_DRV_ERR_MASK, TMC2240_DRV_ERR_SHIFT, TMC2240_GSTAT, false}) +#define TMC2240_UV_CP_MASK 0x00000004 +#define TMC2240_UV_CP_SHIFT 2 +#define TMC2240_UV_CP_FIELD ((RegisterField) {TMC2240_UV_CP_MASK, TMC2240_UV_CP_SHIFT, TMC2240_GSTAT, false}) +#define TMC2240_REGISTER_RESET_MASK 0x00000008 +#define TMC2240_REGISTER_RESET_SHIFT 3 +#define TMC2240_REGISTER_RESET_FIELD ((RegisterField) {TMC2240_REGISTER_RESET_MASK, TMC2240_REGISTER_RESET_SHIFT, TMC2240_GSTAT, false}) +#define TMC2240_VM_UVLO_MASK 0x00000010 +#define TMC2240_VM_UVLO_SHIFT 4 +#define TMC2240_VM_UVLO_FIELD ((RegisterField) {TMC2240_VM_UVLO_MASK, TMC2240_VM_UVLO_SHIFT, TMC2240_GSTAT, false}) +#define TMC2240_IFCNT_MASK 0x000000FF +#define TMC2240_IFCNT_SHIFT 0 +#define TMC2240_IFCNT_FIELD ((RegisterField) {TMC2240_IFCNT_MASK, TMC2240_IFCNT_SHIFT, TMC2240_IFCNT, false}) +#define TMC2240_SLAVEADDR_MASK 0x000000FF +#define TMC2240_SLAVEADDR_SHIFT 0 +#define TMC2240_SLAVEADDR_FIELD ((RegisterField) {TMC2240_SLAVEADDR_MASK, TMC2240_SLAVEADDR_SHIFT, TMC2240_SLAVECONF, false}) +#define TMC2240_SENDDELAY_MASK 0x00000F00 +#define TMC2240_SENDDELAY_SHIFT 8 +#define TMC2240_SENDDELAY_FIELD ((RegisterField) {TMC2240_SENDDELAY_MASK, TMC2240_SENDDELAY_SHIFT, TMC2240_SLAVECONF, false}) +#define TMC2240_STEP_MASK 0x00000001 +#define TMC2240_STEP_SHIFT 0 +#define TMC2240_STEP_FIELD ((RegisterField) {TMC2240_STEP_MASK, TMC2TMC2240_GCONF240_STEP_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_DIR_MASK 0x00000002 +#define TMC2240_DIR_SHIFT 1 +#define TMC2240_DIR_FIELD ((RegisterField) {TMC2240_DIR_MASK, TMC2240_DIR_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_ENCB_MASK 0x00000004 +#define TMC2240_ENCB_SHIFT 2 +#define TMC2240_ENCB_FIELD ((RegisterField) {TMC2240_ENCB_MASK, TMC2240_ENCB_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_ENCA_MASK 0x00000008 +#define TMC2240_ENCA_SHIFT 3 +#define TMC2240_ENCA_FIELD ((RegisterField) {TMC2240_ENCA_MASK, TMC2240_ENCA_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_DRV_ENN_MASK 0x00000010 +#define TMC2240_DRV_ENN_SHIFT 4 +#define TMC2240_DRV_ENN_FIELD ((RegisterField) {TMC2240_DRV_ENN_MASK, TMC2240_DRV_ENN_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_ENCN_MASK 0x00000020 +#define TMC2240_ENCN_SHIFT 5 +#define TMC2240_ENCN_FIELD ((RegisterField) {TMC2240_ENCN_MASK, TMC2240_ENCN_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_UART_EN_MASK 0x00000040 +#define TMC2240_UART_EN_SHIFT 6 +#define TMC2240_UART_EN_FIELD ((RegisterField) {TMC2240_UART_EN_MASK, TMC2240_UART_EN_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_RESERVED_MASK 0x00000080 +#define TMC2240_RESERVED_SHIFT 7 +#define TMC2240_RESERVED_FIELD ((RegisterField) {TMC2240_RESERVED_MASK, TMC2240_RESERVED_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_COMP_A_MASK 0x00000100 +#define TMC2240_COMP_A_SHIFT 8 +#define TMC2240_COMP_A_FIELD ((RegisterField) {TMC2240_COMP_A_MASK, TMC2240_COMP_A_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_COMP_B_MASK 0x00000200 +#define TMC2240_COMP_B_SHIFT 9 +#define TMC2240_COMP_B_FIELD ((RegisterField) {TMC2240_COMP_B_MASK, TMC2240_COMP_B_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_COMP_A1_A2_MASK 0x00000400 +#define TMC2240_COMP_A1_A2_SHIFT 10 +#define TMC2240_COMP_A1_A2_FIELD ((RegisterField) {TMC2240_COMP_A1_A2_MASK, TMC2240_COMP_A1_A2_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_COMP_B1_B2_MASK 0x00000800 +#define TMC2240_COMP_B1_B2_SHIFT 11 +#define TMC2240_COMP_B1_B2_FIELD ((RegisterField) {TMC2240_COMP_B1_B2_MASK, TMC2240_COMP_B1_B2_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_OUTPUT_MASK 0x00001000 +#define TMC2240_OUTPUT_SHIFT 12 +#define TMC2240_OUTPUT_FIELD ((RegisterField) {TMC2240_OUTPUT_MASK, TMC2240_OUTPUT_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_EXT_RES_DET_MASK 0x00002000 +#define TMC2240_EXT_RES_DET_SHIFT 13 +#define TMC2240_EXT_RES_DET_FIELD ((RegisterField) {TMC2240_EXT_RES_DET_MASK, TMC2240_EXT_RES_DET_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_EXT_CLK_MASK 0x00004000 +#define TMC2240_EXT_CLK_SHIFT 14 +#define TMC2240_EXT_CLK_FIELD ((RegisterField) {TMC2240_EXT_CLK_MASK, TMC2240_EXT_CLK_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_ADC_ERR_MASK 0x00008000 +#define TMC2240_ADC_ERR_SHIFT 15 +#define TMC2240_ADC_ERR_FIELD ((RegisterField) {TMC2240_ADC_ERR_MASK, TMC2240_ADC_ERR_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_SILICON_RV_MASK 0x00070000 +#define TMC2240_SILICON_RV_SHIFT 16 +#define TMC2240_SILICON_RV_FIELD ((RegisterField) {TMC2240_SILICON_RV_MASK, TMC2240_SILICON_RV_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_VERSION_MASK 0xFF000000 +#define TMC2240_VERSION_SHIFT 24 +#define TMC2240_VERSION_FIELD ((RegisterField) {TMC2240_VERSION_MASK, TMC2240_VERSION_SHIFT, TMC2240_IOIN, false}) +#define TMC2240_CURRENT_RANGE_MASK 0x00000003 +#define TMC2240_CURRENT_RANGE_SHIFT 0 +#define TMC2240_CURRENT_RANGE_FIELD ((RegisterField) {TMC2240_CURRENT_RANGE_MASK, TMC2240_CURRENT_RANGE_SHIFT, TMC2240_DRV_CONF, false}) +#define TMC2240_SLOPE_CONTROL_MASK 0x00000030 +#define TMC2240_SLOPE_CONTROL_SHIFT 4 +#define TMC2240_SLOPE_CONTROL_FIELD ((RegisterField) {TMC2240_SLOPE_CONTROL_MASK, TMC2240_SLOPE_CONTROL_SHIFT, TMC2240_DRV_CONF, false}) +#define TMC2240_GLOBALSCALER_MASK 0x000000FF +#define TMC2240_GLOBALSCALER_SHIFT 0 +#define TMC2240_GLOBALSCALER_FIELD ((RegisterField) {TMC2240_GLOBALSCALER_MASK, TMC2240_GLOBALSCALER_SHIFT, TMC2240_GLOBAL_SCALER, false}) +#define TMC2240_IHOLD_MASK 0x0000001F +#define TMC2240_IHOLD_SHIFT 0 +#define TMC2240_IHOLD_FIELD ((RegisterField) {TMC2240_IHOLD_MASK, TMC2240_IHOLD_SHIFT, TMC2240_IHOLD_IRUN, false}) +#define TMC2240_IRUN_MASK 0x00001F00 +#define TMC2240_IRUN_SHIFT 8 +#define TMC2240_IRUN_FIELD ((RegisterField) {TMC2240_IRUN_MASK, TMC2240_IRUN_SHIFT, TMC2240_IHOLD_IRUN, false}) +#define TMC2240_IHOLDDELAY_MASK 0x000F0000 +#define TMC2240_IHOLDDELAY_SHIFT 16 +#define TMC2240_IHOLDDELAY_FIELD ((RegisterField) {TMC2240_IHOLDDELAY_MASK, TMC2240_IHOLDDELAY_SHIFT, TMC2240_IHOLD_IRUN, false}) +#define TMC2240_IRUNDELAY_MASK 0x0F000000 +#define TMC2240_IRUNDELAY_SHIFT 24 +#define TMC2240_IRUNDELAY_FIELD ((RegisterField) {TMC2240_IRUNDELAY_MASK, TMC2240_IRUNDELAY_SHIFT, TMC2240_IHOLD_IRUN, false}) +#define TMC2240_TPOWERDOWN_MASK 0x000000FF +#define TMC2240_TPOWERDOWN_SHIFT 0 +#define TMC2240_TPOWERDOWN_FIELD ((RegisterField) {TMC2240_TPOWERDOWN_MASK, TMC2240_TPOWERDOWN_SHIFT, TMC2240_TPOWERDOWN, false}) +#define TMC2240_TSTEP_MASK 0x000FFFFF +#define TMC2240_TSTEP_SHIFT 0 +#define TMC2240_TSTEP_FIELD ((RegisterField) {TMC2240_TSTEP_MASK, TMC2240_TSTEP_SHIFT, TMC2240_TSTEP, false}) +#define TMC2240_TPWMTHRS_MASK 0x000FFFFF +#define TMC2240_TPWMTHRS_SHIFT 0 +#define TMC2240_TPWMTHRS_FIELD ((RegisterField) {TMC2240_TPWMTHRS_MASK, TMC2240_TPWMTHRS_SHIFT, TMC2240_TPWMTHRS, false}) +#define TMC2240_TCOOLTHRS_MASK 0x000FFFFF +#define TMC2240_TCOOLTHRS_SHIFT 0 +#define TMC2240_TCOOLTHRS_FIELD ((RegisterField) {TMC2240_TCOOLTHRS_MASK, TMC2240_TCOOLTHRS_SHIFT, TMC2240_TCOOLTHRS, false}) +#define TMC2240_THIGH_MASK 0x000FFFFF +#define TMC2240_THIGH_SHIFT 0 +#define TMC2240_THIGH_FIELD ((RegisterField) {TMC2240_THIGH_MASK, TMC2240_THIGH_SHIFT, TMC2240_THIGH, false}) +#define TMC2240_DIRECT_COIL_A_MASK 0x000001FF +#define TMC2240_DIRECT_COIL_A_SHIFT 0 +#define TMC2240_DIRECT_COIL_A_FIELD ((RegisterField) {TMC2240_DIRECT_COIL_A_MASK, TMC2240_DIRECT_COIL_A_SHIFT, TMC2240_DIRECT_MODE, true}) +#define TMC2240_DIRECT_COIL_B_MASK 0x01FF0000 +#define TMC2240_DIRECT_COIL_B_SHIFT 16 +#define TMC2240_DIRECT_COIL_B_FIELD ((RegisterField) {TMC2240_DIRECT_COIL_B_MASK, TMC2240_DIRECT_COIL_B_SHIFT, TMC2240_DIRECT_MODE, true}) +#define TMC2240_POL_A_MASK 0x00000001 +#define TMC2240_POL_A_SHIFT 0 +#define TMC2240_POL_A_FIELD ((RegisterField) {TMC2240_POL_A_MASK, TMC2240_POL_A_SHIFT, TMC2240_ENCMODE, false}) +#define TMC2240_POL_B_MASK 0x00000002 +#define TMC2240_POL_B_SHIFT 1 +#define TMC2240_POL_B_FIELD ((RegisterField) {TMC2240_POL_B_MASK, TMC2240_POL_B_SHIFT, TMC2240_ENCMODE, false}) +#define TMC2240_POL_N_MASK 0x00000004 +#define TMC2240_POL_N_SHIFT 2 +#define TMC2240_POL_N_FIELD ((RegisterField) {TMC2240_POL_N_MASK, TMC2240_POL_N_SHIFT, TMC2240_ENCMODE, false}) +#define TMC2240_IGNORE_AB_MASK 0x00000008 +#define TMC2240_IGNORE_AB_SHIFT 3 +#define TMC2240_IGNORE_AB_FIELD ((RegisterField) {TMC2240_IGNORE_AB_MASK, TMC2240_IGNORE_AB_SHIFT, TMC2240_ENCMODE, false}) +#define TMC2240_CLR_CONT_MASK 0x00000010 +#define TMC2240_CLR_CONT_SHIFT 4 +#define TMC2240_CLR_CONT_FIELD ((RegisterField) {TMC2240_CLR_CONT_MASK, TMC2240_CLR_CONT_SHIFT, TMC2240_ENCMODE, false}) +#define TMC2240_CLR_ONCE_MASK 0x00000020 +#define TMC2240_CLR_ONCE_SHIFT 5 +#define TMC2240_CLR_ONCE_FIELD ((RegisterField) {TMC2240_CLR_ONCE_MASK, TMC2240_CLR_ONCE_SHIFT, TMC2240_ENCMODE, false}) +#define TMC2240_POS_NEG_EDGE_MASK 0x000000C0 +#define TMC2240_POS_NEG_EDGE_SHIFT 6 +#define TMC2240_POS_NEG_EDGE_FIELD ((RegisterField) {TMC2240_POS_NEG_EDGE_MASK, TMC2240_POS_NEG_EDGE_SHIFT, TMC2240_ENCMODE, false}) +#define TMC2240_CLR_ENC_X_MASK 0x00000100 +#define TMC2240_CLR_ENC_X_SHIFT 8 +#define TMC2240_CLR_ENC_X_FIELD ((RegisterField) {TMC2240_CLR_ENC_X_MASK, TMC2240_CLR_ENC_X_SHIFT, TMC2240_ENCMODE, false}) +#define TMC2240_LATCH_X_ACT_MASK 0x00000200 +#define TMC2240_LATCH_X_ACT_SHIFT 9 +#define TMC2240_LATCH_X_ACT_FIELD ((RegisterField) {TMC2240_LATCH_X_ACT_MASK, TMC2240_LATCH_X_ACT_SHIFT, TMC2240_ENCMODE, false}) +#define TMC2240_ENC_SEL_DECIMAL_MASK 0x00000400 +#define TMC2240_ENC_SEL_DECIMAL_SHIFT 10 +#define TMC2240_ENC_SEL_DECIMAL_FIELD ((RegisterField) {TMC2240_ENC_SEL_DECIMAL_MASK, TMC2240_ENC_SEL_DECIMAL_SHIFT, TMC2240_ENCMODE, false}) +#define TMC2240_X_ENC_MASK 0xFFFFFFFF +#define TMC2240_X_ENC_SHIFT 0 +#define TMC2240_X_ENC_FIELD ((RegisterField) {TMC2240_X_ENC_MASK, TMC2240_X_ENC_SHIFT, TMC2240_X_ENC, true}) +#define TMC2240_ENC_CONST_MASK 0xFFFFFFFF +#define TMC2240_ENC_CONST_SHIFT 0 +#define TMC2240_ENC_CONST_FIELD ((RegisterField) {TMC2240_ENC_CONST_MASK, TMC2240_ENC_CONST_SHIFT, TMC2240_ENC_CONST, true}) +#define TMC2240_N_EVENT_MASK 0x00000001 +#define TMC2240_N_EVENT_SHIFT 0 +#define TMC2240_N_EVENT_FIELD ((RegisterField) {TMC2240_N_EVENT_MASK, TMC2240_N_EVENT_SHIFT, TMC2240_ENC_STATUS, false}) +#define TMC2240_DEVIATION_WARN_MASK 0x00000002 +#define TMC2240_DEVIATION_WARN_SHIFT 1 +#define TMC2240_DEVIATION_WARN_FIELD ((RegisterField) {TMC2240_DEVIATION_WARN_MASK, TMC2240_DEVIATION_WARN_SHIFT, TMC2240_ENC_STATUS, false}) +#define TMC2240_ENC_LATCH_MASK 0xFFFFFFFF +#define TMC2240_ENC_LATCH_SHIFT 0 +#define TMC2240_ENC_LATCH_FIELD ((RegisterField) {TMC2240_ENC_LATCH_MASK, TMC2240_ENC_LATCH_SHIFT, TMC2240_ENC_LATCH, false}) +#define TMC2240_ADC_VSUPPLY_MASK 0x00001FFF +#define TMC2240_ADC_VSUPPLY_SHIFT 0 +#define TMC2240_ADC_VSUPPLY_FIELD ((RegisterField) {TMC2240_ADC_VSUPPLY_MASK, TMC2240_ADC_VSUPPLY_SHIFT, TMC2240_ADC_VSUPPLY_AIN, true}) +#define TMC2240_ADC_AIN_MASK 0x1FFF0000 +#define TMC2240_ADC_AIN_SHIFT 16 +#define TMC2240_ADC_AIN_FIELD ((RegisterField) {TMC2240_ADC_AIN_MASK, TMC2240_ADC_AIN_SHIFT, TMC2240_ADC_VSUPPLY_AIN, true}) +#define TMC2240_ADC_TEMP_MASK 0x00001FFF +#define TMC2240_ADC_TEMP_SHIFT 0 +#define TMC2240_ADC_TEMP_FIELD ((RegisterField) {TMC2240_ADC_TEMP_MASK, TMC2240_ADC_TEMP_SHIFT, TMC2240_ADC_TEMP, true}) +#define TMC2240_OVERVOLTAGE_VTH_MASK 0x00001FFF +#define TMC2240_OVERVOLTAGE_VTH_SHIFT 0 +#define TMC2240_OVERVOLTAGE_VTH_FIELD ((RegisterField) {TMC2240_OVERVOLTAGE_VTH_MASK, TMC2240_OVERVOLTAGE_VTH_SHIFT, TMC2240_OTW_OV_VTH, false}) +#define TMC2240_OVERTEMPPREWARNING_VTH_MASK 0x1FFF0000 +#define TMC2240_OVERTEMPPREWARNING_VTH_SHIFT 16 +#define TMC2240_OVERTEMPPREWARNING_VTH_FIELD ((RegisterField) {TMC2240_OVERTEMPPREWARNING_VTH_MASK, TMC2240_OVERTEMPPREWARNING_VTH_SHIFT, TMC2240_OTW_OV_VTH, false}) +#define TMC2240_MSLUT___MASK 0xFFFFFFFF +#define TMC2240_MSLUT___SHIFT 0 +#define TMC2240_MSLUT___FIELD ((RegisterField) {TMC2240_MSLUT___MASK, TMC2240_MSLUT___SHIFT, TMC2240_MSLUT[0], false}) +#define TMC2240_W0_MASK 0x00000003 +#define TMC2240_W0_SHIFT 0 +#define TMC2240_W0_FIELD ((RegisterField) {TMC2240_W0_MASK, TMC2240_W0_SHIFT, TMC2240_MSLUTSEL, false}) +#define TMC2240_W1_MASK 0x0000000C +#define TMC2240_W1_SHIFT 2 +#define TMC2240_W1_FIELD ((RegisterField) {TMC2240_W1_MASK, TMC2240_W1_SHIFT, TMC2240_MSLUTSEL, false}) +#define TMC2240_W2_MASK 0x00000030 +#define TMC2240_W2_SHIFT 4 +#define TMC2240_W2_FIELD ((RegisterField) {TMC2240_W2_MASK, TMC2240_W2_SHIFT, TMC2240_MSLUTSEL, false}) +#define TMC2240_W3_MASK 0x000000C0 +#define TMC2240_W3_SHIFT 6 +#define TMC2240_W3_FIELD ((RegisterField) {TMC2240_W3_MASK, TMC2240_W3_SHIFT, TMC2240_MSLUTSEL, false}) +#define TMC2240_X1_MASK 0x0000FF00 +#define TMC2240_X1_SHIFT 8 +#define TMC2240_X1_FIELD ((RegisterField) {TMC2240_X1_MASK, TMC2240_X1_SHIFT, TMC2240_MSLUTSEL, false}) +#define TMC2240_X2_MASK 0x00FF0000 +#define TMC2240_X2_SHIFT 16 +#define TMC2240_X2_FIELD ((RegisterField) {TMC2240_X2_MASK, TMC2240_X2_SHIFT, TMC2240_MSLUTSEL, false}) +#define TMC2240_X3_MASK 0xFF000000 +#define TMC2240_X3_SHIFT 24 +#define TMC2240_X3_FIELD ((RegisterField) {TMC2240_X3_MASK, TMC2240_X3_SHIFT, TMC2240_MSLUTSEL, false}) +#define TMC2240_START_SIN_MASK 0x000000FF +#define TMC2240_START_SIN_SHIFT 0 +#define TMC2240_START_SIN_FIELD ((RegisterField) {TMC2240_START_SIN_MASK, TMC2240_START_SIN_SHIFT, TMC2240_MSLUTSTART, false}) +#define TMC2240_START_SIN90_MASK 0x00FF0000 +#define TMC2240_START_SIN90_SHIFT 16 +#define TMC2240_START_SIN90_FIELD ((RegisterField) {TMC2240_START_SIN90_MASK, TMC2240_START_SIN90_SHIFT, TMC2240_MSLUTSTART, false}) +#define TMC2240_OFFSET_SIN90_MASK 0xFF000000 +#define TMC2240_OFFSET_SIN90_SHIFT 24 +#define TMC2240_OFFSET_SIN90_FIELD ((RegisterField) {TMC2240_OFFSET_SIN90_MASK, TMC2240_OFFSET_SIN90_SHIFT, TMC2240_MSLUTSTART, false}) +#define TMC2240_MSCNT_MASK 0x000003FF +#define TMC2240_MSCNT_SHIFT 0 +#define TMC2240_MSCNT_FIELD ((RegisterField) {TMC2240_MSCNT_MASK, TMC2240_MSCNT_SHIFT, TMC2240_MSCNT, false}) +#define TMC2240_CUR_B_MASK 0x000001FF +#define TMC2240_CUR_B_SHIFT 0 +#define TMC2240_CUR_B_FIELD ((RegisterField) {TMC2240_CUR_B_MASK, TMC2240_CUR_B_SHIFT, TMC2240_MSCURACT, true}) +#define TMC2240_CUR_A_MASK 0x01FF0000 +#define TMC2240_CUR_A_SHIFT 16 +#define TMC2240_CUR_A_FIELD ((RegisterField) {TMC2240_CUR_A_MASK, TMC2240_CUR_A_SHIFT, TMC2240_MSCURACT, true}) +#define TMC2240_TOFF_MASK 0x0000000F +#define TMC2240_TOFF_SHIFT 0 +#define TMC2240_TOFF_FIELD ((RegisterField) {TMC2240_TOFF_MASK, TMC2240_TOFF_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_HSTRT_TFD210_MASK 0x00000070 +#define TMC2240_HSTRT_TFD210_SHIFT 4 +#define TMC2240_HSTRT_TFD210_FIELD ((RegisterField) {TMC2240_HSTRT_TFD210_MASK, TMC2240_HSTRT_TFD210_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_HEND_OFFSET_MASK 0x00000780 +#define TMC2240_HEND_OFFSET_SHIFT 7 +#define TMC2240_HEND_OFFSET_FIELD ((RegisterField) {TMC2240_HEND_OFFSET_MASK, TMC2240_HEND_OFFSET_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_FD3_MASK 0x00000800 +#define TMC2240_FD3_SHIFT 11 +#define TMC2240_FD3_FIELD ((RegisterField) {TMC2240_FD3_MASK, TMC2240_FD3_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_DISFDCC_MASK 0x00001000 +#define TMC2240_DISFDCC_SHIFT 12 +#define TMC2240_DISFDCC_FIELD ((RegisterField) {TMC2240_DISFDCC_MASK, TMC2240_DISFDCC_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_CHM_MASK 0x00004000 +#define TMC2240_CHM_SHIFT 14 +#define TMC2240_CHM_FIELD ((RegisterField) {TMC2240_CHM_MASK, TMC2240_CHM_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_TBL_MASK 0x00018000 +#define TMC2240_TBL_SHIFT 15 +#define TMC2240_TBL_FIELD ((RegisterField) {TMC2240_TBL_MASK, TMC2240_TBL_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_VHIGHFS_MASK 0x00040000 +#define TMC2240_VHIGHFS_SHIFT 18 +#define TMC2240_VHIGHFS_FIELD ((RegisterField) {TMC2240_VHIGHFS_MASK, TMC2240_VHIGHFS_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_VHIGHCHM_MASK 0x00080000 +#define TMC2240_VHIGHCHM_SHIFT 19 +#define TMC2240_VHIGHCHM_FIELD ((RegisterField) {TMC2240_VHIGHCHM_MASK, TMC2240_VHIGHCHM_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_TPFD_MASK 0x00F00000 +#define TMC2240_TPFD_SHIFT 20 +#define TMC2240_TPFD_FIELD ((RegisterField) {TMC2240_TPFD_MASK, TMC2240_TPFD_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_MRES_MASK 0x0F000000 +#define TMC2240_MRES_SHIFT 24 +#define TMC2240_MRES_FIELD ((RegisterField) {TMC2240_MRES_MASK, TMC2240_MRES_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_INTPOL_MASK 0x10000000 +#define TMC2240_INTPOL_SHIFT 28 +#define TMC2240_INTPOL_FIELD ((RegisterField) {TMC2240_INTPOL_MASK, TMC2240_INTPOL_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_DEDGE_MASK 0x20000000 +#define TMC2240_DEDGE_SHIFT 29 +#define TMC2240_DEDGE_FIELD ((RegisterField) {TMC2240_DEDGE_MASK, TMC2240_DEDGE_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_DISS2G_MASK 0x40000000 +#define TMC2240_DISS2G_SHIFT 30 +#define TMC2240_DISS2G_FIELD ((RegisterField) {TMC2240_DISS2G_MASK, TMC2240_DISS2G_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_DISS2VS_MASK 0x80000000 +#define TMC2240_DISS2VS_SHIFT 31 +#define TMC2240_DISS2VS_FIELD ((RegisterField) {TMC2240_DISS2VS_MASK, TMC2240_DISS2VS_SHIFT, TMC2240_CHOPCONF, false}) +#define TMC2240_SEMIN_MASK 0x0000000F +#define TMC2240_SEMIN_SHIFT 0 +#define TMC2240_SEMIN_FIELD ((RegisterField) {TMC2240_SEMIN_MASK, TMC2240_SEMIN_SHIFT, TMC2240_COOLCONF, false}) +#define TMC2240_SEUP_MASK 0x00000060 +#define TMC2240_SEUP_SHIFT 5 +#define TMC2240_SEUP_FIELD ((RegisterField) {TMC2240_SEUP_MASK, TMC2240_SEUP_SHIFT, TMC2240_COOLCONF, false}) +#define TMC2240_SEMAX_MASK 0x00000F00 +#define TMC2240_SEMAX_SHIFT 8 +#define TMC2240_SEMAX_FIELD ((RegisterField) {TMC2240_SEMAX_MASK, TMC2240_SEMAX_SHIFT, TMC2240_COOLCONF, false}) +#define TMC2240_SEDN_MASK 0x00006000 +#define TMC2240_SEDN_SHIFT 13 +#define TMC2240_SEDN_FIELD ((RegisterField) {TMC2240_SEDN_MASK, TMC2240_SEDN_SHIFT, TMC2240_COOLCONF, false}) +#define TMC2240_SEIMIN_MASK 0x00008000 +#define TMC2240_SEIMIN_SHIFT 15 +#define TMC2240_SEIMIN_FIELD ((RegisterField) {TMC2240_SEIMIN_MASK, TMC2240_SEIMIN_SHIFT, TMC2240_COOLCONF, false}) +#define TMC2240_SGT_MASK 0x007F0000 +#define TMC2240_SGT_SHIFT 16 +#define TMC2240_SGT_FIELD ((RegisterField) {TMC2240_SGT_MASK, TMC2240_SGT_SHIFT, TMC2240_COOLCONF, false}) +#define TMC2240_SFILT_MASK 0x01000000 +#define TMC2240_SFILT_SHIFT 24 +#define TMC2240_SFILT_FIELD ((RegisterField) {TMC2240_SFILT_MASK, TMC2240_SFILT_SHIFT, TMC2240_COOLCONF, false}) +#define TMC2240_SG_RESULT_MASK 0x000003FF +#define TMC2240_SG_RESULT_SHIFT 0 +#define TMC2240_SG_RESULT_FIELD ((RegisterField) {TMC2240_SG_RESULT_MASK, TMC2240_SG_RESULT_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_S2VSA_MASK 0x00001000 +#define TMC2240_S2VSA_SHIFT 12 +#define TMC2240_S2VSA_FIELD ((RegisterField) {TMC2240_S2VSA_MASK, TMC2240_S2VSA_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_S2VSB_MASK 0x00002000 +#define TMC2240_S2VSB_SHIFT 13 +#define TMC2240_S2VSB_FIELD ((RegisterField) {TMC2240_S2VSB_MASK, TMC2240_S2VSB_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_STEALTH_MASK 0x00004000 +#define TMC2240_STEALTH_SHIFT 14 +#define TMC2240_STEALTH_FIELD ((RegisterField) {TMC2240_STEALTH_MASK, TMC2240_STEALTH_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_FSACTIVE_MASK 0x00008000 +#define TMC2240_FSACTIVE_SHIFT 15 +#define TMC2240_FSACTIVE_FIELD ((RegisterField) {TMC2240_FSACTIVE_MASK, TMC2240_FSACTIVE_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_CS_ACTUAL_MASK 0x001F0000 +#define TMC2240_CS_ACTUAL_SHIFT 16 +#define TMC2240_CS_ACTUAL_FIELD ((RegisterField) {TMC2240_CS_ACTUAL_MASK, TMC2240_CS_ACTUAL_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_STALLGUARD_MASK 0x01000000 +#define TMC2240_STALLGUARD_SHIFT 24 +#define TMC2240_STALLGUARD_FIELD ((RegisterField) {TMC2240_STALLGUARD_MASK, TMC2240_STALLGUARD_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_OT_MASK 0x02000000 +#define TMC2240_OT_SHIFT 25 +#define TMC2240_OT_FIELD ((RegisterField) {TMC2240_OT_MASK, TMC2240_OT_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_OTPW_MASK 0x04000000 +#define TMC2240_OTPW_SHIFT 26 +#define TMC2240_OTPW_FIELD ((RegisterField) {TMC2240_OTPW_MASK, TMC2240_OTPW_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_S2GA_MASK 0x08000000 +#define TMC2240_S2GA_SHIFT 27 +#define TMC2240_S2GA_FIELD ((RegisterField) {TMC2240_S2GA_MASK, TMC2240_S2GA_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_S2GB_MASK 0x10000000 +#define TMC2240_S2GB_SHIFT 28 +#define TMC2240_S2GB_FIELD ((RegisterField) {TMC2240_S2GB_MASK, TMC2240_S2GB_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_OLA_MASK 0x20000000 +#define TMC2240_OLA_SHIFT 29 +#define TMC2240_OLA_FIELD ((RegisterField) {TMC2240_OLA_MASK, TMC2240_OLA_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_OLB_MASK 0x40000000 +#define TMC2240_OLB_SHIFT 30 +#define TMC2240_OLB_FIELD ((RegisterField) {TMC2240_OLB_MASK, TMC2240_OLB_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_STST_MASK 0x80000000 +#define TMC2240_STST_SHIFT 31 +#define TMC2240_STST_FIELD ((RegisterField) {TMC2240_STST_MASK, TMC2240_STST_SHIFT, TMC2240_DRVSTATUS, false}) +#define TMC2240_PWM_OFS_MASK 0x000000FF +#define TMC2240_PWM_OFS_SHIFT 0 +#define TMC2240_PWM_OFS_FIELD ((RegisterField) {TMC2240_PWM_OFS_MASK, TMC2240_PWM_OFS_SHIFT, TMC2240_PWMCONF, false}) +#define TMC2240_PWM_GRAD_MASK 0x0000FF00 +#define TMC2240_PWM_GRAD_SHIFT 8 +#define TMC2240_PWM_GRAD_FIELD ((RegisterField) {TMC2240_PWM_GRAD_MASK, TMC2240_PWM_GRAD_SHIFT, TMC2240_PWMCONF, false}) +#define TMC2240_PWM_FREQ_MASK 0x00030000 +#define TMC2240_PWM_FREQ_SHIFT 16 +#define TMC2240_PWM_FREQ_FIELD ((RegisterField) {TMC2240_PWM_FREQ_MASK, TMC2240_PWM_FREQ_SHIFT, TMC2240_PWMCONF, false}) +#define TMC2240_PWM_AUTOSCALE_MASK 0x00040000 +#define TMC2240_PWM_AUTOSCALE_SHIFT 18 +#define TMC2240_PWM_AUTOSCALE_FIELD ((RegisterField) {TMC2240_PWM_AUTOSCALE_MASK, TMC2240_PWM_AUTOSCALE_SHIFT, TMC2240_PWMCONF, false}) +#define TMC2240_PWM_AUTOGRAD_MASK 0x00080000 +#define TMC2240_PWM_AUTOGRAD_SHIFT 19 +#define TMC2240_PWM_AUTOGRAD_FIELD ((RegisterField) {TMC2240_PWM_AUTOGRAD_MASK, TMC2240_PWM_AUTOGRAD_SHIFT, TMC2240_PWMCONF, false}) +#define TMC2240_FREEWHEEL_MASK 0x00300000 +#define TMC2240_FREEWHEEL_SHIFT 20 +#define TMC2240_FREEWHEEL_FIELD ((RegisterField) {TMC2240_FREEWHEEL_MASK, TMC2240_FREEWHEEL_SHIFT, TMC2240_PWMCONF, false}) +#define TMC2240_PWM_MEAS_SD_ENABLE_MASK 0x00400000 +#define TMC2240_PWM_MEAS_SD_ENABLE_SHIFT 22 +#define TMC2240_PWM_MEAS_SD_ENABLE_FIELD ((RegisterField) {TMC2240_PWM_MEAS_SD_ENABLE_MASK, TMC2240_PWM_MEAS_SD_ENABLE_SHIFT, TMC2240_PWMCONF, false}) +#define TMC2240_PWM_DIS_REG_STST_MASK 0x00800000 +#define TMC2240_PWM_DIS_REG_STST_SHIFT 23 +#define TMC2240_PWM_DIS_REG_STST_FIELD ((RegisterField) {TMC2240_PWM_DIS_REG_STST_MASK, TMC2240_PWM_DIS_REG_STST_SHIFT, TMC2240_PWMCONF, false}) +#define TMC2240_PWM_REG_MASK 0x0F000000 +#define TMC2240_PWM_REG_SHIFT 24 +#define TMC2240_PWM_REG_FIELD ((RegisterField) {TMC2240_PWM_REG_MASK, TMC2240_PWM_REG_SHIFT, TMC2240_PWMCONF, false}) +#define TMC2240_PWM_LIM_MASK 0xF0000000 +#define TMC2240_PWM_LIM_SHIFT 28 +#define TMC2240_PWM_LIM_FIELD ((RegisterField) {TMC2240_PWM_LIM_MASK, TMC2240_PWM_LIM_SHIFT, TMC2240_PWMCONF, false}) +#define TMC2240_PWM_SCALE_SUM_MASK 0x000003FF +#define TMC2240_PWM_SCALE_SUM_SHIFT 0 +#define TMC2240_PWM_SCALE_SUM_FIELD ((RegisterField) {TMC2240_PWM_SCALE_SUM_MASK, TMC2240_PWM_SCALE_SUM_SHIFT, TMC2240_PWM_SCALE, false}) +#define TMC2240_PWM_SCALE_AUTO_MASK 0x01FF0000 +#define TMC2240_PWM_SCALE_AUTO_SHIFT 16 +#define TMC2240_PWM_SCALE_AUTO_FIELD ((RegisterField) {TMC2240_PWM_SCALE_AUTO_MASK, TMC2240_PWM_SCALE_AUTO_SHIFT, TMC2240_PWM_SCALE, false}) +#define TMC2240_PWM_OFS_AUTO_MASK 0x000000FF +#define TMC2240_PWM_OFS_AUTO_SHIFT 0 +#define TMC2240_PWM_OFS_AUTO_FIELD ((RegisterField) {TMC2240_PWM_OFS_AUTO_MASK, TMC2240_PWM_OFS_AUTO_SHIFT, TMC2240_PWM_AUTO, false}) +#define TMC2240_PWM_GRAD_AUTO_MASK 0x00FF0000 +#define TMC2240_PWM_GRAD_AUTO_SHIFT 16 +#define TMC2240_PWM_GRAD_AUTO_FIELD ((RegisterField) {TMC2240_PWM_GRAD_AUTO_MASK, TMC2240_PWM_GRAD_AUTO_SHIFT, TMC2240_PWM_AUTO, false}) +#define TMC2240_SG4_THRS_MASK 0x000000FF +#define TMC2240_SG4_THRS_SHIFT 0 +#define TMC2240_SG4_THRS_FIELD ((RegisterField) {TMC2240_SG4_THRS_MASK, TMC2240_SG4_THRS_SHIFT, TMC2240_SG4_THRS, false}) +#define TMC2240_SG4_FILT_EN_MASK 0x00000100 +#define TMC2240_SG4_FILT_EN_SHIFT 8 +#define TMC2240_SG4_FILT_EN_FIELD ((RegisterField) {TMC2240_SG4_FILT_EN_MASK, TMC2240_SG4_FILT_EN_SHIFT, TMC2240_SG4_THRS, false}) +#define TMC2240_SG_ANGLE_OFFSET_MASK 0x00000200 +#define TMC2240_SG_ANGLE_OFFSET_SHIFT 9 +#define TMC2240_SG_ANGLE_OFFSET_FIELD ((RegisterField) {TMC2240_SG_ANGLE_OFFSET_MASK, TMC2240_SG_ANGLE_OFFSET_SHIFT, TMC2240_SG4_THRS, false}) +#define TMC2240_SG4_RESULT_MASK 0x000003FF +#define TMC2240_SG4_RESULT_SHIFT 0 +#define TMC2240_SG4_RESULT_FIELD ((RegisterField) {TMC2240_SG4_RESULT_MASK, TMC2240_SG4_RESULT_SHIFT, TMC2240_SG4_RESULT, false}) +#define TMC2240_SG4_IND_0_MASK 0x000000FF +#define TMC2240_SG4_IND_0_SHIFT 0 +#define TMC2240_SG4_IND_0_FIELD ((RegisterField) {TMC2240_SG4_IND_0_MASK, TMC2240_SG4_IND_0_SHIFT, TMC2240_SG4_IND, false}) +#define TMC2240_SG4_IND_1_MASK 0x0000FF00 +#define TMC2240_SG4_IND_1_SHIFT 8 +#define TMC2240_SG4_IND_1_FIELD ((RegisterField) {TMC2240_SG4_IND_1_MASK, TMC2240_SG4_IND_1_SHIFT, TMC2240_SG4_IND, false}) +#define TMC2240_SG4_IND_2_MASK 0x00FF0000 +#define TMC2240_SG4_IND_2_SHIFT 16 +#define TMC2240_SG4_IND_2_FIELD ((RegisterField) {TMC2240_SG4_IND_2_MASK, TMC2240_SG4_IND_2_SHIFT, TMC2240_SG4_IND, false}) +#define TMC2240_SG4_IND_3_MASK 0xFF000000 +#define TMC2240_SG4_IND_3_SHIFT 24 +#define TMC2240_SG4_IND_3_FIELD ((RegisterField) {TMC2240_SG4_IND_3_MASK, TMC2240_SG4_IND_3_SHIFT, TMC2240_SG4_IND, false}) + +#endif diff --git a/firmware/octoaxes/tmc/ic/TMC2660/TMC2660.cpp b/firmware/octoaxes/tmc/ic/TMC2660/TMC2660.cpp new file mode 100644 index 000000000..276cc8c4d --- /dev/null +++ b/firmware/octoaxes/tmc/ic/TMC2660/TMC2660.cpp @@ -0,0 +1,385 @@ +/* + * TMC2660.cpp + * + * TMC2660 stepper driver implementation. + * Communicates through TMC4361A Cover interface. + * + * Created: 2026-01-21 + */ + +#include "TMC2660.h" +#include "../TMC4361A/TMC4361A.h" + +// ============================================================================ +// Cache Implementation +// ============================================================================ + +#if TMC2660_CACHE == 1 +#if TMC2660_ENABLE_TMC_CACHE == 1 + +int32_t tmc2660_shadowRegister[TMC2660_IC_CACHE_COUNT][TMC2660_REGISTER_COUNT]; + +bool tmc2660_cache(uint16_t icID, TMC2660CacheOp operation, uint8_t address, uint32_t *value) +{ + if (operation == TMC2660_CACHE_READ) + { + if (icID >= TMC2660_IC_CACHE_COUNT) + return false; + + // Only non-readable registers use cache + if (TMC2660_IS_READABLE(tmc2660_registerAccess[address])) + return false; + + *value = tmc2660_shadowRegister[icID][address]; + return true; + } + else if (operation == TMC2660_CACHE_WRITE || operation == TMC2660_CACHE_FILL_DEFAULT) + { + if (icID >= TMC2660_IC_CACHE_COUNT) + return false; + + tmc2660_shadowRegister[icID][address] = *value; + return true; + } + + return false; +} + +void tmc2660_initCache(void) +{ + // Initialize shadow registers with preset values + for (uint8_t icID = 0; icID < TMC2660_IC_CACHE_COUNT; icID++) + { + for (uint8_t addr = 0; addr < TMC2660_REGISTER_COUNT; addr++) + { + tmc2660_shadowRegister[icID][addr] = tmc2660_sampleRegisterPreset[addr]; + } + } +} + +#endif +#endif + +// ============================================================================ +// Cover Interface Communication +// ============================================================================ + +// Send datagram through TMC4361A Cover interface +static void sendCoverDatagram(uint8_t icID, uint32_t datagram) +{ + uint8_t data[3]; + + // TMC2660 uses 20-bit datagrams, MSB first + data[0] = (datagram >> 16) & 0xFF; + data[1] = (datagram >> 8) & 0xFF; + data[2] = datagram & 0xFF; + + // Send through TMC4361A Cover interface + tmc4361A_readWriteCover(icID, data, 3); + + // Extract response (20-bit, right-shifted by 4) + uint32_t response = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | data[2]; + response = (response >> 4) & 0xFFFFF; + + // Determine which response register based on RDSEL + uint8_t rdsel = TMC2660_GET_RDSEL(tmc2660_shadowRegister[icID][TMC2660_DRVCONF]); + + // Store response in appropriate shadow register + tmc2660_shadowRegister[icID][rdsel] = response; + tmc2660_shadowRegister[icID][TMC2660_RESPONSE_LATEST] = response; +} + +// ============================================================================ +// Register Read/Write Implementation +// ============================================================================ + +void tmc2660_writeRegister(uint8_t icID, uint8_t address, uint32_t value) +{ + // Only write to write-only registers (addresses 8-F) + if (TMC2660_IS_READONLY_REGISTER(address)) + return; + + // Mask to 20 bits + value &= 0x0FFFFF; + + // Cache the value + tmc2660_cache(icID, TMC2660_CACHE_WRITE, address, &value); + + // Construct datagram: address bits are encoded in the value + // The address mapping: DRVCTRL=8, CHOPCONF=C, SMARTEN=D, SGCSCONF=E, DRVCONF=F + // Real address in datagram: (address & 0x07) << 17 + uint32_t datagram = TMC2660_DATAGRAM((address & 0x07), value); + + // Send through Cover interface + sendCoverDatagram(icID, datagram); +} + +uint32_t tmc2660_readRegister(uint8_t icID, uint8_t address) +{ + if (icID >= TMC2660_IC_CACHE_COUNT || address >= TMC2660_REGISTER_COUNT) + return 0; + + uint32_t value; + + // Read from cache for write-only registers + if (tmc2660_cache(icID, TMC2660_CACHE_READ, address, &value)) + return value; + + // For response registers, return cached value + // (They are updated automatically after each write) + return tmc2660_shadowRegister[icID][address]; +} + +uint8_t tmc2660_getStatusBits(uint8_t icID) +{ + if (icID >= TMC2660_IC_CACHE_COUNT) + return 0; + + return tmc2660_shadowRegister[icID][TMC2660_RESPONSE_LATEST] & TMC2660_STATUS_MASK; +} + +// ============================================================================ +// High-Level Configuration API +// ============================================================================ + +void tmc2660_initDriver(uint8_t icID) +{ + // Initialize cache + tmc2660_initCache(); + + // Write default configuration + // DRVCONF: RDSEL=0 (microstep position), VSENSE=1 (low sense resistor range) + uint32_t drvconf = TMC2660_SET_RDSEL(0) | TMC2660_SET_VSENSE(1); + tmc2660_writeRegister(icID, TMC2660_DRVCONF, drvconf); + + // CHOPCONF: TBL=2, HEND=3, HSTRT=4, TOFF=5 (standard SpreadCycle) + uint32_t chopconf = TMC2660_SET_TBL(2) | TMC2660_SET_HEND(3) | + TMC2660_SET_HSTRT(4) | TMC2660_SET_TOFF(5); + tmc2660_writeRegister(icID, TMC2660_CHOPCONF, chopconf); + + // SGCSCONF: CS=16 (mid current), SGT=0 + uint32_t sgcsconf = TMC2660_SET_CS(16) | TMC2660_SET_SGT(0); + tmc2660_writeRegister(icID, TMC2660_SGCSCONF, sgcsconf); + + // SMARTEN: Disabled by default + uint32_t smarten = 0; + tmc2660_writeRegister(icID, TMC2660_SMARTEN, smarten); + + // DRVCTRL: 256 microsteps, interpolation enabled + uint32_t drvctrl = TMC2660_SET_MRES(0) | TMC2660_SET_INTERPOL(1); + tmc2660_writeRegister(icID, TMC2660_DRVCTRL, drvctrl); +} + +void tmc2660_setRunCurrent(uint8_t icID, uint8_t current) +{ + if (current > 31) current = 31; + + uint32_t value = tmc2660_readRegister(icID, TMC2660_SGCSCONF); + value &= ~TMC2660_SET_CS(0x1F); // Clear CS field + value |= TMC2660_SET_CS(current); + tmc2660_writeRegister(icID, TMC2660_SGCSCONF, value); +} + +void tmc2660_setMicrostepResolution(uint8_t icID, uint8_t mres) +{ + if (mres > 8) mres = 8; + + uint32_t value = tmc2660_readRegister(icID, TMC2660_DRVCTRL); + value &= ~TMC2660_SET_MRES(0x0F); // Clear MRES field + value |= TMC2660_SET_MRES(mres); + tmc2660_writeRegister(icID, TMC2660_DRVCTRL, value); +} + +void tmc2660_setInterpolation(uint8_t icID, bool enable) +{ + uint32_t value = tmc2660_readRegister(icID, TMC2660_DRVCTRL); + value &= ~TMC2660_SET_INTERPOL(1); // Clear INTPOL bit + value |= TMC2660_SET_INTERPOL(enable ? 1 : 0); + tmc2660_writeRegister(icID, TMC2660_DRVCTRL, value); +} + +void tmc2660_setChopperConfig(uint8_t icID, uint8_t toff, uint8_t hstrt, int8_t hend, uint8_t tbl) +{ + // Clamp values + if (toff > 15) toff = 15; + if (hstrt > 7) hstrt = 7; + if (hend < -3) hend = -3; + if (hend > 12) hend = 12; + if (tbl > 3) tbl = 3; + + // HEND is stored as hend + 3 (offset) + uint8_t hend_reg = (uint8_t)(hend + 3); + + uint32_t value = TMC2660_SET_TOFF(toff) | TMC2660_SET_HSTRT(hstrt) | + TMC2660_SET_HEND(hend_reg) | TMC2660_SET_TBL(tbl); + tmc2660_writeRegister(icID, TMC2660_CHOPCONF, value); +} + +void tmc2660_enableDriver(uint8_t icID, bool enable) +{ + uint32_t value = tmc2660_readRegister(icID, TMC2660_CHOPCONF); + + if (enable) + { + // Ensure TOFF > 0 to enable driver + if (TMC2660_GET_TOFF(value) == 0) + { + value |= TMC2660_SET_TOFF(5); // Default TOFF + } + } + else + { + // Set TOFF = 0 to disable driver + value &= ~TMC2660_SET_TOFF(0x0F); + } + + tmc2660_writeRegister(icID, TMC2660_CHOPCONF, value); +} + +void tmc2660_setStallGuardThreshold(uint8_t icID, int8_t threshold) +{ + // Clamp to valid range + if (threshold < -64) threshold = -64; + if (threshold > 63) threshold = 63; + + // SGT is a 7-bit signed value stored in bits 8-14 + uint8_t sgt = (uint8_t)(threshold & 0x7F); + + uint32_t value = tmc2660_readRegister(icID, TMC2660_SGCSCONF); + value &= ~TMC2660_SET_SGT(0x7F); + value |= TMC2660_SET_SGT(sgt); + tmc2660_writeRegister(icID, TMC2660_SGCSCONF, value); +} + +void tmc2660_setStallGuardFilter(uint8_t icID, bool enable) +{ + uint32_t value = tmc2660_readRegister(icID, TMC2660_SGCSCONF); + value &= ~TMC2660_SET_SFILT(1); + value |= TMC2660_SET_SFILT(enable ? 1 : 0); + tmc2660_writeRegister(icID, TMC2660_SGCSCONF, value); +} + +// ============================================================================ +// Status Detection API +// ============================================================================ + +bool tmc2660_isStalled(uint8_t icID) +{ + uint8_t status = tmc2660_getStatusBits(icID); + return TMC2660_GET_SGF(status) != 0; +} + +bool tmc2660_isOvertemperature(uint8_t icID) +{ + uint8_t status = tmc2660_getStatusBits(icID); + return TMC2660_GET_OT(status) != 0; +} + +bool tmc2660_isOvertemperatureWarning(uint8_t icID) +{ + uint8_t status = tmc2660_getStatusBits(icID); + return TMC2660_GET_OTPW(status) != 0; +} + +bool tmc2660_isShortToGroundA(uint8_t icID) +{ + uint8_t status = tmc2660_getStatusBits(icID); + return TMC2660_GET_S2GA(status) != 0; +} + +bool tmc2660_isShortToGroundB(uint8_t icID) +{ + uint8_t status = tmc2660_getStatusBits(icID); + return TMC2660_GET_S2GB(status) != 0; +} + +bool tmc2660_isOpenLoadA(uint8_t icID) +{ + uint8_t status = tmc2660_getStatusBits(icID); + return TMC2660_GET_OLA(status) != 0; +} + +bool tmc2660_isOpenLoadB(uint8_t icID) +{ + uint8_t status = tmc2660_getStatusBits(icID); + return TMC2660_GET_OLB(status) != 0; +} + +bool tmc2660_isStandstill(uint8_t icID) +{ + uint8_t status = tmc2660_getStatusBits(icID); + return TMC2660_GET_STST(status) != 0; +} + +// ============================================================================ +// Diagnostic API +// ============================================================================ + +uint16_t tmc2660_getStallGuardValue(uint8_t icID) +{ + // Request SG2 value by setting RDSEL=1 + uint32_t drvconf = tmc2660_readRegister(icID, TMC2660_DRVCONF); + uint8_t oldRdsel = TMC2660_GET_RDSEL(drvconf); + + // Temporarily switch to RDSEL=1 (StallGuard) + drvconf &= ~TMC2660_SET_RDSEL(0x03); + drvconf |= TMC2660_SET_RDSEL(1); + tmc2660_writeRegister(icID, TMC2660_DRVCONF, drvconf); + + // Read StallGuard value (10-bit, in RESPONSE1) + uint32_t response = tmc2660_readRegister(icID, TMC2660_RESPONSE1); + uint16_t sg2 = TMC2660_GET_SG(response); + + // Restore original RDSEL + drvconf &= ~TMC2660_SET_RDSEL(0x03); + drvconf |= TMC2660_SET_RDSEL(oldRdsel); + tmc2660_writeRegister(icID, TMC2660_DRVCONF, drvconf); + + return sg2; +} + +uint16_t tmc2660_getMicrostepPosition(uint8_t icID) +{ + // Request microstep position by setting RDSEL=0 + uint32_t drvconf = tmc2660_readRegister(icID, TMC2660_DRVCONF); + uint8_t oldRdsel = TMC2660_GET_RDSEL(drvconf); + + // Temporarily switch to RDSEL=0 (Microstep) + drvconf &= ~TMC2660_SET_RDSEL(0x03); + drvconf |= TMC2660_SET_RDSEL(0); + tmc2660_writeRegister(icID, TMC2660_DRVCONF, drvconf); + + // Read microstep position (10-bit, in RESPONSE0) + uint32_t response = tmc2660_readRegister(icID, TMC2660_RESPONSE0); + uint16_t mstep = TMC2660_GET_MSTEP(response); + + // Restore original RDSEL + drvconf &= ~TMC2660_SET_RDSEL(0x03); + drvconf |= TMC2660_SET_RDSEL(oldRdsel); + tmc2660_writeRegister(icID, TMC2660_DRVCONF, drvconf); + + return mstep; +} + +uint8_t tmc2660_getActualCurrentScale(uint8_t icID) +{ + // Request current scale by setting RDSEL=2 + uint32_t drvconf = tmc2660_readRegister(icID, TMC2660_DRVCONF); + uint8_t oldRdsel = TMC2660_GET_RDSEL(drvconf); + + // Temporarily switch to RDSEL=2 (CoolStep) + drvconf &= ~TMC2660_SET_RDSEL(0x03); + drvconf |= TMC2660_SET_RDSEL(2); + tmc2660_writeRegister(icID, TMC2660_DRVCONF, drvconf); + + // Read SE value (5-bit, in RESPONSE2) + uint32_t response = tmc2660_readRegister(icID, TMC2660_RESPONSE2); + uint8_t se = TMC2660_GET_SE(response); + + // Restore original RDSEL + drvconf &= ~TMC2660_SET_RDSEL(0x03); + drvconf |= TMC2660_SET_RDSEL(oldRdsel); + tmc2660_writeRegister(icID, TMC2660_DRVCONF, drvconf); + + return se; +} diff --git a/firmware/octoaxes/tmc/ic/TMC2660/TMC2660.h b/firmware/octoaxes/tmc/ic/TMC2660/TMC2660.h new file mode 100644 index 000000000..59673736b --- /dev/null +++ b/firmware/octoaxes/tmc/ic/TMC2660/TMC2660.h @@ -0,0 +1,322 @@ +/* + * TMC2660.h + * + * TMC2660 stepper driver for Octoaxes project. + * Communicates through TMC4361A Cover interface. + * + * Created: 2026-01-21 + */ + +#ifndef TMC_IC_TMC2660_H_ +#define TMC_IC_TMC2660_H_ + +#include "TMC2660_HW_Abstraction.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================ +// API Configuration +// ============================================================================ + +#ifndef TMC2660_CACHE +#define TMC2660_CACHE 1 +#endif + +#ifndef TMC2660_ENABLE_TMC_CACHE +#define TMC2660_ENABLE_TMC_CACHE 1 +#endif + +// Number of ICs (same as TMC4361A, paired 1:1) +#ifndef TMC2660_IC_CACHE_COUNT +#define TMC2660_IC_CACHE_COUNT 7 +#endif + +// ============================================================================ +// RegisterField Type Definition +// ============================================================================ + +typedef struct { + uint32_t mask; + uint8_t shift; + uint8_t address; + bool isSigned; +} TMC2660RegisterField; + +// ============================================================================ +// Communication Mode +// ============================================================================ + +typedef enum { + TMC2660_COMM_COVER, // Through TMC4361A Cover interface (default) + TMC2660_COMM_DIRECT_SPI // Direct SPI (reserved for future use) +} TMC2660CommMode; + +// ============================================================================ +// Core Register API +// ============================================================================ + +/** + * @brief Write a register to TMC2660 + * @param icID IC identifier (0 to TMC2660_IC_CACHE_COUNT-1) + * @param address Register address + * @param value Value to write (20-bit) + */ +void tmc2660_writeRegister(uint8_t icID, uint8_t address, uint32_t value); + +/** + * @brief Read a register from TMC2660 + * @param icID IC identifier + * @param address Register address + * @return Register value + */ +uint32_t tmc2660_readRegister(uint8_t icID, uint8_t address); + +/** + * @brief Get status bits from last response + * @param icID IC identifier + * @return Status bits (8-bit) + */ +uint8_t tmc2660_getStatusBits(uint8_t icID); + +// ============================================================================ +// Field-Level Operations +// ============================================================================ + +static inline uint32_t tmc2660_fieldExtract(uint32_t data, TMC2660RegisterField field) +{ + uint32_t value = (data & field.mask) >> field.shift; + + if (field.isSigned) { + uint32_t baseMask = field.mask >> field.shift; + uint32_t signMask = baseMask & (~baseMask >> 1); + value = (value ^ signMask) - signMask; + } + + return value; +} + +static inline uint32_t tmc2660_fieldRead(uint8_t icID, TMC2660RegisterField field) +{ + uint32_t value = tmc2660_readRegister(icID, field.address); + return tmc2660_fieldExtract(value, field); +} + +static inline uint32_t tmc2660_fieldUpdate(uint32_t data, TMC2660RegisterField field, uint32_t value) +{ + return (data & (~field.mask)) | ((value << field.shift) & field.mask); +} + +static inline void tmc2660_fieldWrite(uint8_t icID, TMC2660RegisterField field, uint32_t value) +{ + uint32_t regValue = tmc2660_readRegister(icID, field.address); + regValue = tmc2660_fieldUpdate(regValue, field, value); + tmc2660_writeRegister(icID, field.address, regValue); +} + +// ============================================================================ +// High-Level Configuration API +// ============================================================================ + +/** + * @brief Initialize TMC2660 driver with default settings + * @param icID IC identifier + */ +void tmc2660_initDriver(uint8_t icID); + +/** + * @brief Set run current (0-31) + * @param icID IC identifier + * @param current Current scale value (0-31, where 31 = max current) + */ +void tmc2660_setRunCurrent(uint8_t icID, uint8_t current); + +/** + * @brief Set microstep resolution + * @param icID IC identifier + * @param mres Microstep resolution (0=256, 1=128, 2=64, ... 8=1) + */ +void tmc2660_setMicrostepResolution(uint8_t icID, uint8_t mres); + +/** + * @brief Enable/disable microstep interpolation + * @param icID IC identifier + * @param enable true to enable 256 microstep interpolation + */ +void tmc2660_setInterpolation(uint8_t icID, bool enable); + +/** + * @brief Configure chopper parameters + * @param icID IC identifier + * @param toff Off time (1-15, 0=driver disabled) + * @param hstrt Hysteresis start (0-7) + * @param hend Hysteresis end (-3 to 12, add 3 for register value) + * @param tbl Blanking time (0-3) + */ +void tmc2660_setChopperConfig(uint8_t icID, uint8_t toff, uint8_t hstrt, int8_t hend, uint8_t tbl); + +/** + * @brief Enable/disable driver output + * @param icID IC identifier + * @param enable true to enable, false to disable + */ +void tmc2660_enableDriver(uint8_t icID, bool enable); + +/** + * @brief Set StallGuard threshold + * @param icID IC identifier + * @param threshold Threshold value (-64 to 63) + */ +void tmc2660_setStallGuardThreshold(uint8_t icID, int8_t threshold); + +/** + * @brief Enable/disable StallGuard filter + * @param icID IC identifier + * @param enable true to enable filtering + */ +void tmc2660_setStallGuardFilter(uint8_t icID, bool enable); + +// ============================================================================ +// Status Detection API +// ============================================================================ + +/** + * @brief Check if motor is stalled (StallGuard flag) + */ +bool tmc2660_isStalled(uint8_t icID); + +/** + * @brief Check for overtemperature shutdown + */ +bool tmc2660_isOvertemperature(uint8_t icID); + +/** + * @brief Check for overtemperature warning + */ +bool tmc2660_isOvertemperatureWarning(uint8_t icID); + +/** + * @brief Check for short to ground on phase A + */ +bool tmc2660_isShortToGroundA(uint8_t icID); + +/** + * @brief Check for short to ground on phase B + */ +bool tmc2660_isShortToGroundB(uint8_t icID); + +/** + * @brief Check for open load on phase A + */ +bool tmc2660_isOpenLoadA(uint8_t icID); + +/** + * @brief Check for open load on phase B + */ +bool tmc2660_isOpenLoadB(uint8_t icID); + +/** + * @brief Check if motor is at standstill + */ +bool tmc2660_isStandstill(uint8_t icID); + +// ============================================================================ +// Diagnostic API +// ============================================================================ + +/** + * @brief Get StallGuard value (load indicator) + * @param icID IC identifier + * @return StallGuard value (0-1023, higher = lower load) + */ +uint16_t tmc2660_getStallGuardValue(uint8_t icID); + +/** + * @brief Get current microstep position + * @param icID IC identifier + * @return Microstep position (0-1023) + */ +uint16_t tmc2660_getMicrostepPosition(uint8_t icID); + +/** + * @brief Get actual current scale (from CoolStep) + * @param icID IC identifier + * @return Current scale value + */ +uint8_t tmc2660_getActualCurrentScale(uint8_t icID); + +// ============================================================================ +// Cache Implementation +// ============================================================================ + +#if TMC2660_CACHE == 1 +#if TMC2660_ENABLE_TMC_CACHE == 1 + +typedef enum { + TMC2660_CACHE_READ, + TMC2660_CACHE_WRITE, + TMC2660_CACHE_FILL_DEFAULT +} TMC2660CacheOp; + +#define TMC_ACCESS_READ 0x01 +#define TMC_ACCESS_WRITE 0x02 +#define TMC_ACCESS_NONE 0x00 +#define TMC2660_IS_READABLE(x) ((x) & TMC_ACCESS_READ) + +static const uint8_t tmc2660_registerAccess[TMC2660_REGISTER_COUNT] = +{ + TMC_ACCESS_READ, // 0: RESPONSE 0 + TMC_ACCESS_READ, // 1: RESPONSE 1 + TMC_ACCESS_READ, // 2: RESPONSE 2 + TMC_ACCESS_READ, // 3: RESPONSE_LATEST + TMC_ACCESS_NONE, // 4: UNUSED + TMC_ACCESS_NONE, // 5: UNUSED + TMC_ACCESS_NONE, // 6: UNUSED + TMC_ACCESS_NONE, // 7: UNUSED + TMC_ACCESS_WRITE, // 8: DRVCTRL + TMC_ACCESS_NONE, // 9: UNUSED + TMC_ACCESS_NONE, // A: UNUSED + TMC_ACCESS_NONE, // B: UNUSED + TMC_ACCESS_WRITE, // C: CHOPCONF + TMC_ACCESS_WRITE, // D: SMARTEN + TMC_ACCESS_WRITE, // E: SGCSCONF + TMC_ACCESS_WRITE // F: DRVCONF +}; + +static const int32_t tmc2660_sampleRegisterPreset[TMC2660_REGISTER_COUNT] = +{ + 0x00000000, // 0: RESPONSE0 + 0x00000000, // 1: RESPONSE1 + 0x00000000, // 2: RESPONSE2 + 0x00000000, // 3: RESPONSE_LATEST + 0x00000000, // 4: UNUSED + 0x00000000, // 5: UNUSED + 0x00000000, // 6: UNUSED + 0x00000000, // 7: UNUSED + 0x00000000, // 8: DRVCTRL (microstep mode, INTPOL=0, MRES=0) + 0x00000000, // 9: UNUSED + 0x00000000, // A: UNUSED + 0x00000000, // B: UNUSED + 0x00091935, // C: CHOPCONF (TBL=2, HEND=3, HSTRT=4, TOFF=5) + 0x000A0000, // D: SMARTEN (disabled) + 0x000D0505, // E: SGCSCONF (CS=5, SGT=5) + 0x000EF040 // F: DRVCONF (RDSEL=0, VSENSE=1) +}; + +extern int32_t tmc2660_shadowRegister[TMC2660_IC_CACHE_COUNT][TMC2660_REGISTER_COUNT]; + +bool tmc2660_cache(uint16_t icID, TMC2660CacheOp operation, uint8_t address, uint32_t *value); +void tmc2660_initCache(void); + +#endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* TMC_IC_TMC2660_H_ */ diff --git a/firmware/octoaxes/tmc/ic/TMC2660/TMC2660_HW_Abstraction.h b/firmware/octoaxes/tmc/ic/TMC2660/TMC2660_HW_Abstraction.h new file mode 100644 index 000000000..11fdc9cee --- /dev/null +++ b/firmware/octoaxes/tmc/ic/TMC2660/TMC2660_HW_Abstraction.h @@ -0,0 +1,298 @@ +/******************************************************************************* +* Copyright © 2019 TRINAMIC Motion Control GmbH & Co. KG +* (now owned by Analog Devices Inc.), +* +* Copyright © 2025 Analog Devices Inc. All Rights Reserved. +* This software is proprietary to Analog Devices, Inc. and its licensors. +*******************************************************************************/ + +#ifndef TMC2660_HW_ABSTRACTION +#define TMC2660_HW_ABSTRACTION + +//Constants + +#define TMC2660_REGISTER_COUNT 16 // Actual Count is 8, but due to mapping, we write 16 +#define TMC2660_MOTORS 1 +#define TMC2660_WRITE_BIT 0x08 +#define TMC2660_ADDRESS_MASK 0x07 +#define TMC2660_ADDRESS_SHIFT 20 +#define TMC2660_STATUS_MASK 0xFF +#define TMC2660_VALUE_MASK 0xFFFFF +#define TMC2660_VALUE_SHIFT 0 +#define TMC2660_MAX_VELOCITY (int32_t) 2147483647 +#define TMC2660_MAX_ACCELERATION (uint32_t) 16777215uL + +#define TMC2660_IS_WRITEONLY_REGISTER(addr) ((addr & TMC2660_WRITE_BIT) == 1) +#define TMC2660_IS_READONLY_REGISTER(addr) ((addr & TMC2660_WRITE_BIT) == 0) + +#define TMC2660_IS_WRITE(datagram) ((datagram) >> (TMC2660_ADDRESS_SHIFT + 3)) +#define TMC2660_ADDRESS(datagram) (((datagram) >> TMC2660_ADDRESS_SHIFT) & TMC2660_ADDRESS_MASK) +#define TMC2660_VALUE(datagram) ((datagram) & TMC2660_VALUE_MASK) + +// Helper macro to determine register address out of write datagram +#define TMC2660_GET_ADDRESS(datagram) ((uint8_t) ((((datagram) >> 18) ? ((datagram)>>17) : 0) & 0x07)) + +// Helper macro to construct the datagram out of the address and the value +#define TMC2660_DATAGRAM(addr, value) (((addr) << 17) | (value)) + +//registers definitions + +// addresses out auf address bits from write datagrams +#define TMC2660_RESPONSE0 0x00 +#define TMC2660_RESPONSE1 0x01 +#define TMC2660_RESPONSE2 0x02 +#define TMC2660_RESPONSE_LATEST 0x03 + +// Addresses of the write-only registers +// Note: This software abstraction maps the registers to addresses 8 and up +#define TMC2660_WRITE_ONLY_REGISTER 0x08 +#define TMC2660_DRVCTRL (0x00 | TMC2660_WRITE_ONLY_REGISTER) // 8 +#define TMC2660_CHOPCONF (0x04 | TMC2660_WRITE_ONLY_REGISTER) // C +#define TMC2660_SMARTEN (0x05 | TMC2660_WRITE_ONLY_REGISTER) // D +#define TMC2660_SGCSCONF (0x06 | TMC2660_WRITE_ONLY_REGISTER) // E +#define TMC2660_DRVCONF (0x07 | TMC2660_WRITE_ONLY_REGISTER) // F + + +//fields definitions + +#define TMC2660_MSTEP_MASK 0x000FFC00 +#define TMC2660_MSTEP_SHIFT 10 +#define TMC2660_MSTEP_FIELD ((TMC2660RegisterField) {TMC2660_MSTEP_MASK, TMC2660_MSTEP_SHIFT, TMC2660_RESPONSE0, false}) +#define TMC2660_SE_MASK 0x00007C00 +#define TMC2660_SE_SHIFT 10 +#define TMC2660_SE_FIELD ((TMC2660RegisterField) {TMC2660_SE_MASK, TMC2660_SE_SHIFT, TMC2660_RESPONSE2, false}) +#define TMC2660_SGU_MASK 0x0F8000 +#define TMC2660_SGU_SHIFT 15 +#define TMC2660_SGU_FIELD ((TMC2660RegisterField) {TMC2660_SGU_MASK, TMC2660_SGU_SHIFT, TMC2660_RESPONSE2, false}) +#define TMC2660_SG2_MASK 0x0FFC00 +#define TMC2660_SG2_SHIFT 10 +#define TMC2660_SG2_FIELD ((TMC2660RegisterField) {TMC2660_SG2_MASK, TMC2660_SG2_SHIFT, TMC2660_RESPONSE1, false}) +#define TMC2660_REGISTER_ADDRESS_BITS_MASK 0x000C0000 +#define TMC2660_REGISTER_ADDRESS_BITS_SHIFT 18 +#define TMC2660_REGISTER_ADDRESS_BITS_FIELD ((TMC2660RegisterField) {TMC2660_REGISTER_ADDRESS_BITS_MASK, TMC2660_REGISTER_ADDRESS_BITS_SHIFT, TMC2660_DRVCTRL, false}) +#define TMC2660_INTPOL_MASK 0x00000200 +#define TMC2660_INTPOL_SHIFT 9 +#define TMC2660_INTPOL_FIELD ((TMC2660RegisterField) {TMC2660_INTPOL_MASK, TMC2660_INTPOL_SHIFT, TMC2660_DRVCTRL, false}) +#define TMC2660_DEDGE_MASK 0x00000100 +#define TMC2660_DEDGE_SHIFT 8 +#define TMC2660_DEDGE_FIELD ((TMC2660RegisterField) {TMC2660_DEDGE_MASK, TMC2660_DEDGE_SHIFT, TMC2660_DRVCTRL, false}) +#define TMC2660_MRES_MASK 0x0000000F +#define TMC2660_MRES_SHIFT 0 +#define TMC2660_MRES_FIELD ((TMC2660RegisterField) {TMC2660_MRES_MASK, TMC2660_MRES_SHIFT, TMC2660_DRVCTRL, false}) +//#define TMC2660_REGISTER_ADDRESS_BITS_MASK 0x000E0000 +//#define TMC2660_REGISTER_ADDRESS_BITS_SHIFT 17 +//#define TMC2660_REGISTER_ADDRESS_BITS_FIELD ((TMC2660RegisterField) {TMC2660_REGISTER_ADDRESS_BITS_MASK, TMC2660_REGISTER_ADDRESS_BITS_SHIFT, TMC2660_DRVCTRL, false}) +#define TMC2660_PHA_MASK 0x00020000 +#define TMC2660_PHA_SHIFT 17 +#define TMC2660_PHA_FIELD ((TMC2660RegisterField) {TMC2660_PHA_MASK, TMC2660_PHA_SHIFT, TMC2660_DRVCTRL, false}) +#define TMC2660_CA_MASK 0x0001FE00 +#define TMC2660_CA_SHIFT 9 +#define TMC2660_CA_FIELD ((TMC2660RegisterField) {TMC2660_CA_MASK, TMC2660_CA_SHIFT, TMC2660_DRVCTRL, false}) +#define TMC2660_PHB_MASK 0x00000100 +#define TMC2660_PHB_SHIFT 8 +#define TMC2660_PHB_FIELD ((TMC2660RegisterField) {TMC2660_PHB_MASK, TMC2660_PHB_SHIFT, TMC2660_DRVCTRL, false}) +#define TMC2660_CB_MASK 0x000000FF +#define TMC2660_CB_SHIFT 0 +#define TMC2660_CB_FIELD ((TMC2660RegisterField) {TMC2660_CB_MASK, TMC2660_CB_SHIFT, TMC2660_DRVCTRL, false}) +//#define TMC2660_REGISTER_ADDRESS_BITS_MASK 0x000E0000 +//#define TMC2660_REGISTER_ADDRESS_BITS_SHIFT 17 +//#define TMC2660_REGISTER_ADDRESS_BITS_FIELD ((TMC2660RegisterField) {TMC2660_REGISTER_ADDRESS_BITS_MASK, TMC2660_REGISTER_ADDRESS_BITS_SHIFT, TMC2660_CHOPCONF, false}) +#define TMC2660_TBL_MASK 0x00018000 +#define TMC2660_TBL_SHIFT 15 +#define TMC2660_TBL_FIELD ((TMC2660RegisterField) {TMC2660_TBL_MASK, TMC2660_TBL_SHIFT, TMC2660_CHOPCONF, false}) +#define TMC2660_CHM_MASK 0x00004000 +#define TMC2660_CHM_SHIFT 14 +#define TMC2660_CHM_FIELD ((TMC2660RegisterField) {TMC2660_CHM_MASK, TMC2660_CHM_SHIFT, TMC2660_CHOPCONF, false}) +#define TMC2660_RNDTF_MASK 0x00002000 +#define TMC2660_RNDTF_SHIFT 13 +#define TMC2660_RNDTF_FIELD ((TMC2660RegisterField) {TMC2660_RNDTF_MASK, TMC2660_RNDTF_SHIFT, TMC2660_CHOPCONF, false}) +#define TMC2660_HDEC_MASK 0x00001800 +#define TMC2660_HDEC_SHIFT 11 +#define TMC2660_HDEC_FIELD ((TMC2660RegisterField) {TMC2660_HDEC_MASK, TMC2660_HDEC_SHIFT, TMC2660_CHOPCONF, false}) +#define TMC2660_HEND_MASK 0x00000780 +#define TMC2660_HEND_SHIFT 7 +#define TMC2660_HEND_FIELD ((TMC2660RegisterField) {TMC2660_HEND_MASK, TMC2660_HEND_SHIFT, TMC2660_CHOPCONF, false}) +#define TMC2660_HSTRT_MASK 0x00000070 +#define TMC2660_HSTRT_SHIFT 4 +#define TMC2660_HSTRT_FIELD ((TMC2660RegisterField) {TMC2660_HSTRT_MASK, TMC2660_HSTRT_SHIFT, TMC2660_CHOPCONF, false}) +#define TMC2660_HDEC1_MASK 0x00001000 +#define TMC2660_HDEC1_SHIFT 12 +#define TMC2660_HDEC1_FIELD ((TMC2660RegisterField) {TMC2660_HDEC1_MASK, TMC2660_HDEC1_SHIFT, TMC2660_CHOPCONF, false}) +#define TMC2660_HDEC0_MASK 0x00000800 +#define TMC2660_HDEC0_SHIFT 11 +#define TMC2660_HDEC0_FIELD ((TMC2660RegisterField) {TMC2660_HDEC0_MASK, TMC2660_HDEC0_SHIFT, TMC2660_CHOPCONF, false}) +#define TMC2660_TOFF_MASK 0x0000000F +#define TMC2660_TOFF_SHIFT 0 +#define TMC2660_TOFF_FIELD ((TMC2660RegisterField) {TMC2660_TOFF_MASK, TMC2660_TOFF_SHIFT, TMC2660_CHOPCONF, false}) +//#define TMC2660_REGISTER_ADDRESS_BITS_MASK 0x000E0000 +//#define TMC2660_REGISTER_ADDRESS_BITS_SHIFT 17 +//#define TMC2660_REGISTER_ADDRESS_BITS_FIELD ((TMC2660RegisterField) {TMC2660_REGISTER_ADDRESS_BITS_MASK, TMC2660_REGISTER_ADDRESS_BITS_SHIFT, TMC2660_SMARTEN, false}) +#define TMC2660_SEIMIN_MASK 0x00008000 +#define TMC2660_SEIMIN_SHIFT 15 +#define TMC2660_SEIMIN_FIELD ((TMC2660RegisterField) {TMC2660_SEIMIN_MASK, TMC2660_SEIMIN_SHIFT, TMC2660_SMARTEN, false}) +#define TMC2660_SEDN_MASK 0x00006000 +#define TMC2660_SEDN_SHIFT 13 +#define TMC2660_SEDN_FIELD ((TMC2660RegisterField) {TMC2660_SEDN_MASK, TMC2660_SEDN_SHIFT, TMC2660_SMARTEN, false}) +#define TMC2660_SEUP_MASK 0x00000060 +#define TMC2660_SEUP_SHIFT 5 +#define TMC2660_SEUP_FIELD ((TMC2660RegisterField) {TMC2660_SEUP_MASK, TMC2660_SEUP_SHIFT, TMC2660_SMARTEN, false}) +#define TMC2660_SEMAX_MASK 0x00000F00 +#define TMC2660_SEMAX_SHIFT 8 +#define TMC2660_SEMAX_FIELD ((TMC2660RegisterField) {TMC2660_SEMAX_MASK, TMC2660_SEMAX_SHIFT, TMC2660_SMARTEN, false}) +#define TMC2660_SEMIN_MASK 0x0000000F +#define TMC2660_SEMIN_SHIFT 0 +#define TMC2660_SEMIN_FIELD ((TMC2660RegisterField) {TMC2660_SEMIN_MASK, TMC2660_SEMIN_SHIFT, TMC2660_SMARTEN, false}) +//#define TMC2660_REGISTER_ADDRESS_BITS_MASK 0x000E0000 +//#define TMC2660_REGISTER_ADDRESS_BITS_SHIFT 17 +//#define TMC2660_REGISTER_ADDRESS_BITS_FIELD ((TMC2660RegisterField) {TMC2660_REGISTER_ADDRESS_BITS_MASK, TMC2660_REGISTER_ADDRESS_BITS_SHIFT, TMC2660_SGCSCONF, false}) +#define TMC2660_SFILT_MASK 0x00010000 +#define TMC2660_SFILT_SHIFT 16 +#define TMC2660_SFILT_FIELD ((TMC2660RegisterField) {TMC2660_SFILT_MASK, TMC2660_SFILT_SHIFT, TMC2660_SGCSCONF, false}) +#define TMC2660_SGT_MASK 0x00007F00 +#define TMC2660_SGT_SHIFT 8 +#define TMC2660_SGT_FIELD ((TMC2660RegisterField) {TMC2660_SGT_MASK, TMC2660_SGT_SHIFT, TMC2660_SGCSCONF, false}) +#define TMC2660_CS_MASK 0x0000001F +#define TMC2660_CS_SHIFT 0 +#define TMC2660_CS_FIELD ((TMC2660RegisterField) {TMC2660_CS_MASK, TMC2660_CS_SHIFT, TMC2660_SGCSCONF, false}) +//#define TMC2660_REGISTER_ADDRESS_BITS_MASK 0x000E0000 +//#define TMC2660_REGISTER_ADDRESS_BITS_SHIFT 17 +//#define TMC2660_REGISTER_ADDRESS_BITS_FIELD ((TMC2660RegisterField) {TMC2660_REGISTER_ADDRESS_BITS_MASK, TMC2660_REGISTER_ADDRESS_BITS_SHIFT, TMC2660_DRVCONF, false}) +#define TMC2660_TST_MASK 0x00010000 +#define TMC2660_TST_SHIFT 16 +#define TMC2660_TST_FIELD ((TMC2660RegisterField) {TMC2660_TST_MASK, TMC2660_TST_SHIFT, TMC2660_DRVCONF, false}) +#define TMC2660_SLPH_MASK 0x0000C000 +#define TMC2660_SLPH_SHIFT 14 +#define TMC2660_SLPH_FIELD ((TMC2660RegisterField) {TMC2660_SLPH_MASK, TMC2660_SLPH_SHIFT, TMC2660_DRVCONF, false}) +#define TMC2660_SLPL_MASK 0x00003000 +#define TMC2660_SLPL_SHIFT 12 +#define TMC2660_SLPL_FIELD ((TMC2660RegisterField) {TMC2660_SLPL_MASK, TMC2660_SLPL_SHIFT, TMC2660_DRVCONF, false}) +#define TMC2660_DISS2G_MASK 0x00000400 +#define TMC2660_DISS2G_SHIFT 10 +#define TMC2660_DISS2G_FIELD ((TMC2660RegisterField) {TMC2660_DISS2G_MASK, TMC2660_DISS2G_SHIFT, TMC2660_DRVCONF, false}) +#define TMC2660_TS2G_MASK 0x00000300 +#define TMC2660_TS2G_SHIFT 8 +#define TMC2660_TS2G_FIELD ((TMC2660RegisterField) {TMC2660_TS2G_MASK, TMC2660_TS2G_SHIFT, TMC2660_DRVCONF, false}) +#define TMC2660_SDOFF_MASK 0x00000080 +#define TMC2660_SDOFF_SHIFT 7 +#define TMC2660_SDOFF_FIELD ((TMC2660RegisterField) {TMC2660_SDOFF_MASK, TMC2660_SDOFF_SHIFT, TMC2660_DRVCONF, false}) +#define TMC2660_VSENSE_MASK 0x00000040 +#define TMC2660_VSENSE_SHIFT 6 +#define TMC2660_VSENSE_FIELD ((TMC2660RegisterField) {TMC2660_VSENSE_MASK, TMC2660_VSENSE_SHIFT, TMC2660_DRVCONF, false}) +#define TMC2660_RDSEL_MASK 0x00000030 +#define TMC2660_RDSEL_SHIFT 4 +#define TMC2660_RDSEL_FIELD ((TMC2660RegisterField) {TMC2660_RDSEL_MASK, TMC2660_RDSEL_SHIFT, TMC2660_DRVCONF, false}) + +// makro function to shift register data fields to correct position with masking to add them to a write datagram like : +// write &= ~TMC2660_SET_CB(-1); // clearing CB field of write datagram to DRVCTRL register +// write |= TMC2660_SET_CB(5); // setting value 5 to CB field of write datagram to DRVCTRL register + +// for clearing use: + +#define TMC2660_SET_CB(X) (((X) & 0xFF) << 0) +#define TMC2660_SET_PHB(X) (((X) & 0x01) << 0) +#define TMC2660_SET_CA(X) (((X) & 0xFF) << 9) +#define TMC2660_SET_PHA(X) (((X) & 0xFF) << 17) +#define TMC2660_SET_MRES(X) (((X) & 0x0F) << 0) + +#define TMC2660_SET_DEDGE(X) (((X) & 0x01) << 8) +#define TMC2660_SET_INTERPOL(X) (((X) & 0x01) << 9) + +// TMC2660_CHOPCONF +#define TMC2660_SET_TOFF(X) (((X) & 0x0F) << 0) +#define TMC2660_SET_HSTRT(X) (((X) & 0x07) << 4) +#define TMC2660_SET_HEND(X) (((X) & 0x0F) << 7) +#define TMC2660_SET_HDEC(X) (((X) & 0x03) << 11) +#define TMC2660_SET_RNDTF(X) (((X) & 0x01) << 13) +#define TMC2660_SET_CHM(X) (((X) & 0x01) << 14) +#define TMC2660_SET_TBL(X) (((X) & 0x03) << 15) + +// TMC2660_SMARTEN +#define TMC2660_SET_SEMIN(X) (((X) & 0x0F) << 0) +#define TMC2660_SET_SEUP(X) (((X) & 0x03) << 5) +#define TMC2660_SET_SEMAX(X) (((X) & 0x0F) << 8) +#define TMC2660_SET_SEDN(X) (((X) & 0x03) << 13) +#define TMC2660_SET_SEIMIN(X) (((X) & 0x01) << 15) + + +// TMC2660_SGCSCONF +#define TMC2660_SET_CS(X) (((X) & 0x1F) << 0) +#define TMC2660_SET_SGT(X) (((X) & 0x7F) << 8) +#define TMC2660_SET_SFILT(X) (((X) & 0x01) << 16) + +// TMC2660_DRVCONF +#define TMC2660_SET_RDSEL(X) (((X) & 0x03) << 4) +#define TMC2660_SET_VSENSE(X) (((X) & 0x01) << 6) +#define TMC2660_SET_SDOFF(X) (((X) & 0x01) << 7) +#define TMC2660_SET_TS2G(X) (((X) & 0x03) << 8) +#define TMC2660_SET_DISS2G(X) (((X) & 0x01) << 10) +#define TMC2660_SET_SLPL(X) (((X) & 0x03) << 12) +#define TMC2660_SET_SLPH(X) (((X) & 0x03) << 14) +#define TMC2660_SET_TST(X) (((X) & 0x01) << 16) + +// makro function to shift register data fields to correct position with masking to read out values out of write datagram + +// cb = TMC2660_GET_CB(write); // reading CB field of write datagram to DRVCTRL register + +// TMC2660_DRVCTRL +#define TMC2660_GET_CB(X) (0xFF & ((X) >> 0)) +#define TMC2660_GET_PHB(X) (0x01 & ((X) >> 0)) +#define TMC2660_GET_CA(X) (0xFF & ((X) >> 9)) +#define TMC2660_GET_PHA(X) (0xFF & ((X) >> 17)) +#define TMC2660_GET_MRES(X) (0x0F & ((X) >> 0)) + +#define TMC2660_GET_DEDGE(X) (0x01 & ((X) >> 8)) +#define TMC2660_GET_INTERPOL(X) (0x01 & ((X) >> 9)) + +// TMC2660_CHOPCONF +#define TMC2660_GET_TOFF(X) (0x0F & ((X) >> 0)) +#define TMC2660_GET_HSTRT(X) (0x07 & ((X) >> 4)) +#define TMC2660_GET_HEND(X) (0x0F & ((X) >> 7)) +#define TMC2660_GET_HDEC(X) (0x03 & ((X) >> 11)) +#define TMC2660_GET_RNDTF(X) (0x01 & ((X) >> 13)) +#define TMC2660_GET_CHM(X) (0x01 & ((X) >> 14)) +#define TMC2660_GET_TBL(X) (0x03 & ((X) >> 15)) + +// TMC2660_SMARTEN +#define TMC2660_GET_SEMIN(X) (0x0F & ((X) >> 0)) +#define TMC2660_GET_SEUP(X) (0x03 & ((X) >> 5)) +#define TMC2660_GET_SEMAX(X) (0x0F & ((X) >> 8)) +#define TMC2660_GET_SEDN(X) (0x03 & ((X) >> 13)) +#define TMC2660_GET_SEIMIN(X) (0x01 & ((X) >> 15)) + +// TMC2660_SGCSCONF +#define TMC2660_GET_CS(X) (0x1F & ((X) >> 0)) +#define TMC2660_GET_SGT(X) (0x7F & ((X) >> 8)) +#define TMC2660_GET_SFILT(X) (0x01 & ((X) >> 16)) + +// TMC2660_DRVCONF +#define TMC2660_GET_RDSEL(X) (0x03 & ((X) >> 4)) +#define TMC2660_GET_VSENSE(X) (0x01 & ((X) >> 6)) +#define TMC2660_GET_SDOFF(X) (0x01 & ((X) >> 7)) +#define TMC2660_GET_TS2G(X) (0x03 & ((X) >> 8)) +#define TMC2660_GET_DISS2G(X) (0x01 & ((X) >> 10)) +#define TMC2660_GET_SLPL(X) (0x03 & ((X) >> 12)) +#define TMC2660_GET_SLPH(X) (0x03 & ((X) >> 14)) +#define TMC2660_GET_TST(X) (0x01 & ((X) >> 16)) + +// makro function to shift register data fields to correct position with masking to read out values out of read datagram + +// TMC2660_RESPONSE0 +#define TMC2660_GET_MSTEP(X) (0x3FF & ((X) >> 10)) + +// TMC2660_RESPONSE1 +#define TMC2660_GET_SG(X) (0x3FF & ((X) >> 10)) + +// TMC2660_RESPONSE2 +#define TMC2660_GET_SGU(X) (0x1F & ((X) >> 15)) +#define TMC2660_GET_SE(X) (0x1F & ((X) >> 10)) + +// General status bits (contained in all TMC2660_RESPONSE0, 1 and 2) +#define TMC2660_GET_STST(X) (0x01 & ((X) >> 7)) +#define TMC2660_GET_OLB(X) (0x01 & ((X) >> 6)) +#define TMC2660_GET_OLA(X) (0x01 & ((X) >> 5)) +#define TMC2660_GET_S2GB(X) (0x01 & ((X) >> 4)) +#define TMC2660_GET_S2GA(X) (0x01 & ((X) >> 3)) +#define TMC2660_GET_OTPW(X) (0x01 & ((X) >> 2)) +#define TMC2660_GET_OT(X) (0x01 & ((X) >> 1)) +#define TMC2660_GET_SGF(X) (0x01 & ((X) >> 0)) +#endif diff --git a/firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A.cpp b/firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A.cpp new file mode 100644 index 000000000..c38737174 --- /dev/null +++ b/firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A.cpp @@ -0,0 +1,272 @@ +/* + * TMC4361A.cpp + * + * TMC4361A motion controller driver implementation. + * Based on official TMC-API, adapted for multi-IC support. + * + * Created: 2026-01-21 + */ + +#include "TMC4361A.h" +#include + +// ============================================================================ +// Cache Implementation +// ============================================================================ + +#if TMC4361A_CACHE == 1 +#if TMC4361A_ENABLE_TMC_CACHE == 1 + +// Cache storage +uint8_t tmc4361A_dirtyBits[TMC4361A_IC_CACHE_COUNT][TMC4361A_REGISTER_COUNT / 8] = {0}; +int32_t tmc4361A_shadowRegister[TMC4361A_IC_CACHE_COUNT][TMC4361A_REGISTER_COUNT]; + +void tmc4361A_setDirtyBit(uint16_t icID, uint8_t index, bool value) +{ + if (index >= TMC4361A_REGISTER_COUNT || icID >= TMC4361A_IC_CACHE_COUNT) + return; + + uint8_t *tmp = &tmc4361A_dirtyBits[icID][index / 8]; + uint8_t shift = (index % 8); + uint8_t mask = 1 << shift; + *tmp = (((*tmp) & (~mask)) | ((value ? 1 : 0) << shift)); +} + +bool tmc4361A_getDirtyBit(uint16_t icID, uint8_t index) +{ + if (index >= TMC4361A_REGISTER_COUNT || icID >= TMC4361A_IC_CACHE_COUNT) + return false; + + uint8_t *tmp = &tmc4361A_dirtyBits[icID][index / 8]; + uint8_t shift = (index % 8); + return ((*tmp) >> shift) & 1; +} + +bool tmc4361A_cache(uint16_t icID, TMC4361ACacheOp operation, uint8_t address, uint32_t *value) +{ + if (operation == TMC4361A_CACHE_READ) + { + // Only supported chips have a cache + if (icID >= TMC4361A_IC_CACHE_COUNT) + return false; + + // Only non-readable registers need caching + if (TMC4361A_IS_READABLE(tmc4361A_registerAccess[address])) + return false; + + // Grab the value from cache + *value = tmc4361A_shadowRegister[icID][address]; + return true; + } + else if (operation == TMC4361A_CACHE_WRITE || operation == TMC4361A_CACHE_FILL_DEFAULT) + { + // Only supported chips have a cache + if (icID >= TMC4361A_IC_CACHE_COUNT) + return false; + + // Write to shadow register + tmc4361A_shadowRegister[icID][address] = *value; + + // Mark dirty only for actual writes (not default fills) + if (operation == TMC4361A_CACHE_WRITE) + { + tmc4361A_setDirtyBit(icID, address, true); + } + return true; + } + + return false; +} + +void tmc4361A_initCache(void) +{ + // Check if we have constants defined + if (ARRAY_SIZE(tmc4361A_RegisterConstants) == 0) + return; + + size_t i, j, id; + + for (i = 0, j = 0; i < TMC4361A_REGISTER_COUNT; i++) + { + // Only handle hardware preset, write-only registers + if (tmc4361A_registerAccess[i] != TMC4361A_ACCESS_W_PRESET) + continue; + + // Search constant list for current address + while (j < ARRAY_SIZE(tmc4361A_RegisterConstants) && + (tmc4361A_RegisterConstants[j].address < i)) + j++; + + // Abort at end of constant list + if (j == ARRAY_SIZE(tmc4361A_RegisterConstants)) + break; + + // If we have an entry, fill the cache + if (tmc4361A_RegisterConstants[j].address == i) + { + for (id = 0; id < TMC4361A_IC_CACHE_COUNT; id++) + { + uint32_t temp = tmc4361A_RegisterConstants[j].value; + tmc4361A_cache(id, TMC4361A_CACHE_FILL_DEFAULT, i, &temp); + } + } + } +} + +#else +// User must implement their own cache +#endif // TMC4361A_ENABLE_TMC_CACHE + +#else +// No cache - stub implementation +static inline bool tmc4361A_cache(uint16_t icID, TMC4361ACacheOp operation, + uint8_t address, uint32_t *value) +{ + (void)icID; + (void)address; + (void)operation; + (void)value; + return false; +} +#endif // TMC4361A_CACHE + +// ============================================================================ +// SPI Read/Write Implementation +// ============================================================================ + +static int32_t readRegisterSPI(uint16_t icID, uint8_t address); +static void writeRegisterSPI(uint16_t icID, uint8_t address, int32_t value); + +int32_t tmc4361A_readRegister(uint16_t icID, uint8_t address) +{ + uint32_t value; + + // Read from cache for write-only registers + if (tmc4361A_cache(icID, TMC4361A_CACHE_READ, address, &value)) + return value; + + return readRegisterSPI(icID, address); +} + +void tmc4361A_writeRegister(uint16_t icID, uint8_t address, int32_t value) +{ + writeRegisterSPI(icID, address, value); +} + +static void writeRegisterSPI(uint16_t icID, uint8_t address, int32_t value) +{ + uint8_t data[5] = {0}; + + data[0] = address | TMC4361A_WRITE_BIT; + data[1] = 0xFF & (value >> 24); + data[2] = 0xFF & (value >> 16); + data[3] = 0xFF & (value >> 8); + data[4] = 0xFF & (value >> 0); + + // Send write request via HAL callback + tmc4361A_readWriteSPI(icID, &data[0], sizeof(data)); + + // Update status + tmc4361A_setStatus(icID, &data[0]); + + // Update cache + tmc4361A_cache(icID, TMC4361A_CACHE_WRITE, address, (uint32_t *)&value); +} + +static int32_t readRegisterSPI(uint16_t icID, uint8_t address) +{ + uint8_t data[5] = {0}; + + // Clear write bit + address = address & TMC4361A_ADDRESS_MASK; + + // First SPI transfer: send read request + data[0] = address; + tmc4361A_readWriteSPI(icID, &data[0], sizeof(data)); + + // Second SPI transfer: receive read reply + data[0] = address; + tmc4361A_readWriteSPI(icID, &data[0], sizeof(data)); + + // Update status + tmc4361A_setStatus(icID, &data[0]); + + // Combine bytes to 32-bit value + return ((int32_t)data[1] << 24) | + ((int32_t)data[2] << 16) | + ((int32_t)data[3] << 8) | + ((int32_t)data[4]); +} + +// ============================================================================ +// Cover Interface (for TMC2660 / TMC2240 communication) +// ============================================================================ + +void tmc4361A_readWriteCover(uint16_t icID, uint8_t *data, size_t length) +{ + // Wait helper + auto waitCover = []() { + volatile uint32_t dummy; + for (uint32_t i = 0; i < 100; i++) { + dummy = i; + } + (void)dummy; + }; + + if (length >= 5) + { + // ==================================================================== + // TMC2240: 40-bit cover datagram (5 bytes) + // data[0] = address byte (bit 7 = write flag) + // data[1..4] = 32-bit data (MSB first) + // ==================================================================== + + // write COVER_HIGH first (address byte) + int32_t coverHigh = (int32_t)data[0]; + tmc4361A_writeRegister(icID, TMC4361A_COVER_HIGH, coverHigh); + + // then write COVER_LOW (32-bit data) -- writing COVER_LOW triggers the SPI transfer + int32_t coverLow = ((int32_t)data[1] << 24) | + ((int32_t)data[2] << 16) | + ((int32_t)data[3] << 8) | + ((int32_t)data[4]); + tmc4361A_writeRegister(icID, TMC4361A_COVER_LOW, coverLow); + + // wait for the transfer to complete (40-bit needs a longer wait than 20-bit) + delayMicroseconds(50); + + // read the response + int32_t responseHigh = tmc4361A_readRegister(icID, TMC4361A_COVER_DRV_HIGH); + int32_t responseLow = tmc4361A_readRegister(icID, TMC4361A_COVER_DRV_LOW); + + data[0] = (uint8_t)(responseHigh & 0xFF); + data[1] = (responseLow >> 24) & 0xFF; + data[2] = (responseLow >> 16) & 0xFF; + data[3] = (responseLow >> 8) & 0xFF; + data[4] = (responseLow >> 0) & 0xFF; + } + else if (length >= 3) + { + // ==================================================================== + // TMC2660: 20-bit cover datagram (3 bytes, padded to 24 bits) + // ==================================================================== + + // Write to COVER_LOW register (lower 24 bits of cover datagram) + int32_t coverValue = ((int32_t)data[0] << 16) | + ((int32_t)data[1] << 8) | + ((int32_t)data[2]); + + tmc4361A_writeRegister(icID, TMC4361A_COVER_LOW, coverValue); + + // Wait for cover transfer to complete + waitCover(); + + // Read response from COVER_DRV_LOW + int32_t response = tmc4361A_readRegister(icID, TMC4361A_COVER_DRV_LOW); + + // Extract response bytes + data[0] = (response >> 16) & 0xFF; + data[1] = (response >> 8) & 0xFF; + data[2] = (response >> 0) & 0xFF; + } +} diff --git a/firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A.h b/firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A.h new file mode 100644 index 000000000..e1f90c25e --- /dev/null +++ b/firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A.h @@ -0,0 +1,287 @@ +/* + * TMC4361A.h + * + * TMC4361A motion controller driver based on official TMC-API. + * Adapted for multi-IC support with icID-based addressing. + * + * Created: 2026-01-21 + */ + +#ifndef TMC_IC_TMC4361A_H_ +#define TMC_IC_TMC4361A_H_ + +#include "TMC4361A_HW_Abstraction.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================ +// API Configuration +// ============================================================================ + +// Enable cache mechanism +#ifndef TMC4361A_CACHE +#define TMC4361A_CACHE 1 +#endif + +// Use TMC-API built-in cache implementation +#ifndef TMC4361A_ENABLE_TMC_CACHE +#define TMC4361A_ENABLE_TMC_CACHE 1 +#endif + +// Number of ICs to support in cache (7 axes: X, Y, Z, W, E1, E3, E4) +#ifndef TMC4361A_IC_CACHE_COUNT +#define TMC4361A_IC_CACHE_COUNT 7 +#endif + +// ============================================================================ +// RegisterField Type Definition (shared with TMC2240) +// ============================================================================ + +#ifndef REGISTER_FIELD_DEFINED +#define REGISTER_FIELD_DEFINED +typedef struct { + uint32_t mask; + uint8_t shift; + uint8_t address; + bool isSigned; +} RegisterField; +#endif + +// ============================================================================ +// SPI Callback Declarations (implemented in HAL layer) +// ============================================================================ + +extern void tmc4361A_readWriteSPI(uint16_t icID, uint8_t *data, size_t dataLength); +extern void tmc4361A_setStatus(uint16_t icID, uint8_t *data); + +// ============================================================================ +// Register Read/Write API +// ============================================================================ + +/** + * @brief Read a register from TMC4361A + * @param icID IC identifier (0 to TMC4361A_IC_CACHE_COUNT-1) + * @param address Register address (0x00 to 0x7F) + * @return Register value (32-bit) + */ +int32_t tmc4361A_readRegister(uint16_t icID, uint8_t address); + +/** + * @brief Write a register to TMC4361A + * @param icID IC identifier + * @param address Register address + * @param value Value to write (32-bit) + */ +void tmc4361A_writeRegister(uint16_t icID, uint8_t address, int32_t value); + +/** + * @brief Read/Write through Cover interface (for TMC2660 communication) + * @param icID IC identifier + * @param data Data buffer + * @param length Data length + */ +void tmc4361A_readWriteCover(uint16_t icID, uint8_t *data, size_t length); + +// ============================================================================ +// Field-Level Operations +// ============================================================================ + +/** + * @brief Extract field value from register data + * @param data Raw register data + * @param field Field definition + * @return Extracted field value + */ +static inline uint32_t tmc4361A_fieldExtract(uint32_t data, RegisterField field) +{ + uint32_t value = (data & field.mask) >> field.shift; + + if (field.isSigned) + { + // Apply signedness conversion + uint32_t baseMask = field.mask >> field.shift; + uint32_t signMask = baseMask & (~baseMask >> 1); + value = (value ^ signMask) - signMask; + } + + return value; +} + +/** + * @brief Read and extract a field from a register + * @param icID IC identifier + * @param field Field definition + * @return Field value + */ +static inline uint32_t tmc4361A_fieldRead(uint16_t icID, RegisterField field) +{ + uint32_t value = tmc4361A_readRegister(icID, field.address); + return tmc4361A_fieldExtract(value, field); +} + +/** + * @brief Update a field in register data + * @param data Current register data + * @param field Field definition + * @param value New field value + * @return Updated register data + */ +static inline uint32_t tmc4361A_fieldUpdate(uint32_t data, RegisterField field, uint32_t value) +{ + return (data & (~field.mask)) | ((value << field.shift) & field.mask); +} + +/** + * @brief Read-modify-write a field in a register + * @param icID IC identifier + * @param field Field definition + * @param value New field value + */ +static inline void tmc4361A_fieldWrite(uint16_t icID, RegisterField field, uint32_t value) +{ + uint32_t regValue = tmc4361A_readRegister(icID, field.address); + regValue = tmc4361A_fieldUpdate(regValue, field, value); + tmc4361A_writeRegister(icID, field.address, regValue); +} + +// ============================================================================ +// Cache Implementation +// ============================================================================ + +#if TMC4361A_CACHE == 1 +#if TMC4361A_ENABLE_TMC_CACHE == 1 + +typedef enum { + TMC4361A_CACHE_READ, + TMC4361A_CACHE_WRITE, + // Fill cache without marking dirty (for hardware defaults) + TMC4361A_CACHE_FILL_DEFAULT +} TMC4361ACacheOp; + +typedef struct { + uint8_t address; + uint32_t value; +} TMC4361ARegisterConstant; + +// Access permission flags +#define TMC4361A_ACCESS_DIRTY 0x08 +#define TMC4361A_ACCESS_READ 0x01 +#define TMC_ACCESS_WRITE 0x02 +#define TMC4361A_ACCESS_W_PRESET 0x42 +#define TMC_IS_RESETTABLE(x) (((x) & (TMC4361A_ACCESS_W_PRESET)) == TMC_ACCESS_WRITE) +#define TMC4361A_IS_READABLE(x) ((x) & TMC4361A_ACCESS_READ) +#define TMC_IS_WRITABLE(x) ((x) & TMC_ACCESS_WRITE) +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +// Default register values +#define R10 0x00040001 // STP_LENGTH_ADD +#define R20 0x00000001 // RAMPMODE +#define R28 0x00013880 // AMAX +#define R29 0x00013880 // DMAX +#define R2D 0x000003E8 // BOW1 +#define R2E 0x000003E8 // BOW2 +#define R2F 0x000003E8 // BOW3 +#define R30 0x000003E8 // BOW4 +#define R54 0x00009C40 // ENC_IN_RES +#define ____ 0x00 +#ifndef N_A +#define N_A 0x00 +#endif + +// Sample register preset values +static const int32_t tmc4361A_sampleRegisterPreset[TMC4361A_REGISTER_COUNT] = +{ +// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F + 0, 0, 0, 0, 0, 0, N_A, N_A, 0, 0, N_A, N_A, 0, 0, 0, 0, // 0x00 - 0x0F + R10, 0, N_A, 0, 0, 0, 0, 0, 0, 0, 0, 0, N_A, 0, 0, N_A, // 0x10 - 0x1F + R20, 0, 0, 0, 0, 0, 0, 0, R28, R29, 0, 0, 0, R2D, R2E, R2F, // 0x20 - 0x2F + R30, N_A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x30 - 0x3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 - 0x4F + 0, 0, 0, N_A, R54, 0, N_A, N_A, N_A, 0, 0, 0, 0, 0, 0, 0, // 0x50 - 0x5F + 0, 0, N_A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x6F + N_A, N_A, N_A, N_A, N_A, N_A, N_A, N_A, N_A, 0, 0, N_A, N_A, 0, N_A, 0 // 0x70 - 0x7F +}; + +#undef R10 +#undef R20 +#undef R28 +#undef R29 +#undef R2D +#undef R2E +#undef R2F +#undef R30 +#undef R54 + +// Register access permissions +static const uint8_t tmc4361A_registerAccess[TMC4361A_REGISTER_COUNT] = +{ +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x43, 0x03, 0x03, 0x03, 0x03, 0x03, 0x43, 0x43, 0x03, 0x03, 0x43, 0x43, 0x03, 0x03, 0x23, 0x01, // 0x00 - 0x0F + 0x03, 0x03, 0x43, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x43, 0x03, 0x03, 0x43, // 0x10 - 0x1F + 0x03, 0x03, 0x01, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // 0x20 - 0x2F + 0x03, 0x43, 0x03, 0x03, 0x03, 0x03, 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // 0x30 - 0x3F + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // 0x40 - 0x4F + 0x03, 0x13, 0x13, 0x42, 0x13, 0x02, 0x42, 0x42, 0x42, 0x03, 0x13, 0x13, 0x02, 0x13, 0x02, 0x02, // 0x50 - 0x5F + 0x02, 0x02, 0x42, 0x02, ____, 0x01, 0x01, 0x02, 0x02, 0x02, 0x01, 0x01, 0x13, 0x13, 0x01, 0x01, // 0x60 - 0x6F + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x13, 0x01, 0x13, 0x13, 0x02, 0x42, 0x01 // 0x70 - 0x7F +}; + +#undef ____ + +// Register constants for preset write-only registers +static const TMC4361ARegisterConstant tmc4361A_RegisterConstants[] = +{ + { 0x53, 0xFFFFFFFF }, // ENC_POS_DEV_TOL + { 0x56, 0x00A000A0 }, // SER_CLK_IN_HIGH, SER_CLK_IN_LOW + { 0x57, 0x00F00000 }, // SSI_IN_CLK_DELAY, SSI_IN_WTIME + { 0x58, 0x00000190 }, // SER_PTIME + { 0x62, 0x00FFFFFF }, // ENC_VEL_ZERO + { 0x70, 0xAAAAB554 }, // MSLUT[0] + { 0x71, 0x4A9554AA }, // MSLUT[1] + { 0x72, 0x24492929 }, // MSLUT[2] + { 0x73, 0x10104222 }, // MSLUT[3] + { 0x74, 0xFBFFFFFF }, // MSLUT[4] + { 0x75, 0xB5BB777D }, // MSLUT[5] + { 0x76, 0x49295556 }, // MSLUT[6] + { 0x77, 0x00404222 }, // MSLUT[7] + { 0x78, 0xFFFF8056 }, // MSLUTSEL + { 0x7E, 0x00F70000 }, // START_SIN, START_SIN_90_120, DAC_OFFSET +}; + +// Cache storage (extern, defined in .cpp) +extern uint8_t tmc4361A_dirtyBits[TMC4361A_IC_CACHE_COUNT][TMC4361A_REGISTER_COUNT/8]; +extern int32_t tmc4361A_shadowRegister[TMC4361A_IC_CACHE_COUNT][TMC4361A_REGISTER_COUNT]; + +/** + * @brief Set dirty bit for a register + */ +void tmc4361A_setDirtyBit(uint16_t icID, uint8_t index, bool value); + +/** + * @brief Get dirty bit for a register + */ +bool tmc4361A_getDirtyBit(uint16_t icID, uint8_t index); + +/** + * @brief Cache operation function + */ +bool tmc4361A_cache(uint16_t icID, TMC4361ACacheOp operation, uint8_t address, uint32_t *value); + +/** + * @brief Initialize cache with default values + */ +void tmc4361A_initCache(void); + +#endif // TMC4361A_ENABLE_TMC_CACHE +#endif // TMC4361A_CACHE + +#ifdef __cplusplus +} +#endif + +#endif /* TMC_IC_TMC4361A_H_ */ diff --git a/firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A_HW_Abstraction.h b/firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A_HW_Abstraction.h new file mode 100644 index 000000000..abd41398e --- /dev/null +++ b/firmware/octoaxes/tmc/ic/TMC4361A/TMC4361A_HW_Abstraction.h @@ -0,0 +1,1416 @@ +/******************************************************************************* +* Copyright © 2019 TRINAMIC Motion Control GmbH & Co. KG +* (now owned by Analog Devices Inc.), +* +* Copyright © 2024 Analog Devices Inc. All Rights Reserved. +* This software is proprietary to Analog Devices, Inc. and its licensors. +*******************************************************************************/ + + +#ifndef TMC4361A_HW_ABSTRACTION +#define TMC4361A_HW_ABSTRACTION + +// Constants in TMC4361A + +#define TMC4361A_REGISTER_COUNT 128 +#define TMC4361A_MOTORS 1 +#define TMC4361A_WRITE_BIT 0x80 +#define TMC4361A_ADDRESS_MASK 0x7F +#define TMC4361A_MAX_VELOCITY (int32_t) 2147483647 +#define TMC4361A_MAX_ACCELERATION (uint32_t) 16777215uL + +#define TMC4361A_COVER_DONE (1<<25) + +#define TMC4361A_RAMP_HOLD 0 +#define TMC4361A_RAMP_TRAPEZ 1 +#define TMC4361A_RAMP_SSHAPE 2 + +#define TMC4361A_RAMP_POSITION 4 + +// Registers in TMC4361A + +#define TMC4361A_GENERAL_CONF 0x00 +#define TMC4361A_REFERENCE_CONF 0x01 +#define TMC4361A_START_CONF 0x02 +#define TMC4361A_INPUT_FILT_CONF 0x03 +#define TMC4361A_SPI_OUT_CONF 0x04 +#define TMC4361A_CURRENT_CONF 0x05 +#define TMC4361A_SCALE_VALUES 0x06 +#define TMC4361A_ENC_IN_CONF 0x07 +#define TMC4361A_ENC_IN_DATA 0x08 +#define TMC4361A_ENC_OUT_DATA 0x09 +#define TMC4361A_STEP_CONF 0x0A +#define TMC4361A_SPI_STATUS_SELECTION 0x0B +#define TMC4361A_EVENT_CLEAR_CONF 0x0C +#define TMC4361A_INTR_CONF 0x0D +#define TMC4361A_EVENTS 0x0E +#define TMC4361A_STATUS 0x0F +#define TMC4361A_STP_LENGTH_ADD 0x10 +#define TMC4361A_DIR_SETUP_TIME 0x10 +#define TMC4361A_START_OUT_ADD 0x11 +#define TMC4361A_GEAR_RATIO 0x12 +#define TMC4361A_START_DELAY 0x13 +#define TMC4361A_CLK_GATING_DELAY 0x14 +#define TMC4361A_STDBY_DELAY 0x15 +#define TMC4361A_FREEWHEEL_DELAY 0x16 +#define TMC4361A_VDRV_SCALE_LIMIT 0x17 +#define TMC4361A_PWM_VMAX 0x17 +#define TMC4361A_UP_SCALE_DELAY 0x18 +#define TMC4361A_CL_UPSCALE_DELAY 0x18 +#define TMC4361A_HOLD_SCALE_DELAY 0x19 +#define TMC4361A_CL_DNSCALE_DELAY 0x19 +#define TMC4361A_DRV_SCALE_DELAY 0x1A +#define TMC4361A_BOOST_TIME 0x1B +#define TMC4361A_CL_ANGLES 0x1C +#define TMC4361A_SPI_SWITCH_VEL 0x1D +#define TMC4361A_DAC_ADDR 0x1D +#define TMC4361A_HOME_SAFETY_MARGIN 0x1E +#define TMC4361A_PWM_FREQ 0x1F +#define TMC4361A_CHOPSYNC_DIV 0x1F +#define TMC4361A_RAMPMODE 0x20 +#define TMC4361A_XACTUAL 0x21 +#define TMC4361A_VACTUAL 0x22 +#define TMC4361A_AACTUAL 0x23 +#define TMC4361A_VMAX 0x24 +#define TMC4361A_VSTART 0x25 +#define TMC4361A_VSTOP 0x26 +#define TMC4361A_VBREAK 0x27 +#define TMC4361A_AMAX 0x28 +#define TMC4361A_DMAX 0x29 +#define TMC4361A_ASTART 0x2A +#define TMC4361A_DFINAL 0x2B +#define TMC4361A_DSTOP 0x2C +#define TMC4361A_BOW1 0x2D +#define TMC4361A_BOW2 0x2E +#define TMC4361A_BOW3 0x2F +#define TMC4361A_BOW4 0x30 +#define TMC4361A_CLK_FREQ 0x31 +#define TMC4361A_POS_COMP 0x32 +#define TMC4361A_VIRT_STOP_LEFT 0x33 +#define TMC4361A_VIRT_STOP_RIGHT 0x34 +#define TMC4361A_X_HOME 0x35 +#define TMC4361A_X_LATCH 0x36 +#define TMC4361A_REV_CNT 0x36 +#define TMC4361A_X_RANGE 0x36 +#define TMC4361A_XTARGET 0x37 +#define TMC4361A_X_PIPE0 0x38 +#define TMC4361A_X_PIPE1 0x39 +#define TMC4361A_X_PIPE2 0x3A +#define TMC4361A_X_PIPE3 0x3B +#define TMC4361A_X_PIPE4 0x3C +#define TMC4361A_X_PIPE5 0x3D +#define TMC4361A_X_PIPE6 0x3E +#define TMC4361A_X_PIPE7 0x3F +#define TMC4361A_SH_REG0 0x40 +#define TMC4361A_SH_REG1 0x41 +#define TMC4361A_SH_REG2 0x42 +#define TMC4361A_SH_REG3 0x43 +#define TMC4361A_SH_REG4 0x44 +#define TMC4361A_SH_REG5 0x45 +#define TMC4361A_SH_REG6 0x46 +#define TMC4361A_SH_REG7 0x47 +#define TMC4361A_SH_REG8 0x48 +#define TMC4361A_SH_REG9 0x49 +#define TMC4361A_SH_REG10 0x4A +#define TMC4361A_SH_REG11 0x4B +#define TMC4361A_SH_REG12 0x4C +#define TMC4361A_SH_REG13 0x4D +#define TMC4361A_FREEZE_REGISTERS 0x4E +#define TMC4361A_CLK_GATING 0x4F +#define TMC4361A_SW_RESET 0x4F +#define TMC4361A_ENC_POS 0x50 +#define TMC4361A_ENC_LATCH 0x51 +#define TMC4361A_ENC_RESET_VAL 0x51 +#define TMC4361A_ENC_POS_DEV 0x52 +#define TMC4361A_CL_TR_TOLERANCE 0x52 +#define TMC4361A_ENC_POS_DEV_TOL 0x53 +#define TMC4361A_ENC_IN_RES 0x54 +#define TMC4361A_ENC_CONST 0x54 +#define TMC4361A_ENC_OUT_RES 0x55 +#define TMC4361A_SER_CLK_IN_HIGH_LOW 0x56 +#define TMC4361A_SSI_IN_CLK_DELAY 0x57 +#define TMC4361A_SSI_IN_WTIME 0x57 +#define TMC4361A_SER_PTIME 0x58 +#define TMC4361A_CL_OFFSET 0x59 +#define TMC4361A_PID_VEL 0x5A +#define TMC4361A_PID_P 0x5A +#define TMC4361A_CL_VMAX_CALC_P 0x5A +#define TMC4361A_PID_ISUM_RD 0x5B +#define TMC4361A_PID_I 0x5B +#define TMC4361A_CL_VMAX_CALC_I 0x5B +#define TMC4361A_PID_D 0x5C +#define TMC4361A_CL_DELTA_P 0x5C +#define TMC4361A_PID_E 0x5D +#define TMC4361A_PID_I_CLIP 0x5D +#define TMC4361A_PID_D_CLKDIV 0x5D +#define TMC4361A_PID_DV_CLIP 0x5E +#define TMC4361A_PID_TOLERANCE 0x5F +#define TMC4361A_CL_TOLERANCE 0x5F +#define TMC4361A_FS_VEL 0x60 +#define TMC4361A_DC_VEL 0x60 +#define TMC4361A_CL_VMIN_EMF 0x60 +#define TMC4361A_DC_TIME 0x61 +#define TMC4361A_DC_SG 0x61 +#define TMC4361A_DC_BLKTIME 0x61 +#define TMC4361A_CL_VADD_EMF 0x61 +#define TMC4361A_DC_LSPTM 0x62 +#define TMC4361A_ENC_VEL_ZERO 0x62 +#define TMC4361A_ENC_VMEAN_WAIT 0x63 +#define TMC4361A_ENC_VMEAN_FILTER 0x63 +#define TMC4361A_ENC_VMEAN_INT 0x63 +#define TMC4361A_ENC_SER_ENC_VARIATION 0x63 +#define TMC4361A_CL_CYCLE 0x63 +#define TMC4361A_V_ENC 0x65 +#define TMC4361A_V_ENC_MEAN 0x66 +#define TMC4361A_VSTALL_LIMIT 0x67 +#define TMC4361A_ADDR_TO_ENC 0x68 +#define TMC4361A_DATA_TO_ENC 0x69 +#define TMC4361A_ADDR_FROM_ENC 0x6A +#define TMC4361A_DATA_FROM_ENC 0x6B +#define TMC4361A_COVER_LOW 0x6C +#define TMC4361A_POLLING_STATUS 0x6C +#define TMC4361A_COVER_HIGH 0x6D +#define TMC4361A_POLLING_REG 0x6D +#define TMC4361A_COVER_DRV_LOW 0x6E +#define TMC4361A_COVER_DRV_HIGH 0x6F +#define TMC4361A_MSLUT_0 0x70 +#define TMC4361A_MSLUT_1 0x71 +#define TMC4361A_MSLUT_2 0x72 +#define TMC4361A_MSLUT_3 0x73 +#define TMC4361A_MSLUT_4 0x74 +#define TMC4361A_MSLUT_5 0x75 +#define TMC4361A_MSLUT_6 0x76 +#define TMC4361A_MSLUT_7 0x77 +#define TMC4361A_MSLUTSEL 0x78 +#define TMC4361A_MSCNT 0x79 +#define TMC4361A_MSOFFSET 0x79 +#define TMC4361A_CURRENTA 0x7A +#define TMC4361A_CURRENTB 0x7A +#define TMC4361A_CURRENTA_SPI 0x7B +#define TMC4361A_CURRENTB_SPI 0x7B +#define TMC4361A_TZEROWAIT 0x7B +#define TMC4361A_SCALE_PARAM 0x7C +#define TMC4361A_CIRCULAR_DEC 0x7C +#define TMC4361A_ENC_COMP_XOFFSET 0x7D +#define TMC4361A_ENC_COMP_YOFFSET 0x7D +#define TMC4361A_ENC_COMP_AMPL 0x7D +#define TMC4361A_START_SIN 0x7E +#define TMC4361A_START_SIN90_120 0x7E +#define TMC4361A_DAC_OFFSET 0x7E +#define TMC4361A_VERSION_NO 0x7F + + +// Fields in TMC4361A + +#define TMC4361A_USE_ASTART_AND_VSTART_MASK 0x00000001 +#define TMC4361A_USE_ASTART_AND_VSTART_SHIFT 0 +#define TMC4361A_USE_ASTART_AND_VSTART_FIELD ((RegisterField) {TMC4361A_USE_ASTART_AND_VSTART_MASK, TMC4361A_USE_ASTART_AND_VSTART_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_DIRECT_ACC_VAL_EN_MASK 0x00000002 +#define TMC4361A_DIRECT_ACC_VAL_EN_SHIFT 1 +#define TMC4361A_DIRECT_ACC_VAL_EN_FIELD ((RegisterField) {TMC4361A_DIRECT_ACC_VAL_EN_MASK, TMC4361A_DIRECT_ACC_VAL_EN_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_DIRECT_BOW_VAL_EN_MASK 0x00000004 +#define TMC4361A_DIRECT_BOW_VAL_EN_SHIFT 2 +#define TMC4361A_DIRECT_BOW_VAL_EN_FIELD ((RegisterField) {TMC4361A_DIRECT_BOW_VAL_EN_MASK, TMC4361A_DIRECT_BOW_VAL_EN_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_STEP_INACTIVE_POL_MASK 0x00000008 +#define TMC4361A_STEP_INACTIVE_POL_SHIFT 3 +#define TMC4361A_STEP_INACTIVE_POL_FIELD ((RegisterField) {TMC4361A_STEP_INACTIVE_POL_MASK, TMC4361A_STEP_INACTIVE_POL_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_TOGGLE_STEP_MASK 0x00000010 +#define TMC4361A_TOGGLE_STEP_SHIFT 4 +#define TMC4361A_TOGGLE_STEP_FIELD ((RegisterField) {TMC4361A_TOGGLE_STEP_MASK, TMC4361A_TOGGLE_STEP_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_POL_DIR_OUT_MASK 0x00000020 +#define TMC4361A_POL_DIR_OUT_SHIFT 5 +#define TMC4361A_POL_DIR_OUT_FIELD ((RegisterField) {TMC4361A_POL_DIR_OUT_MASK, TMC4361A_POL_DIR_OUT_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_SDIN_MODE_MASK 0x000000c0 +#define TMC4361A_SDIN_MODE_SHIFT 6 +#define TMC4361A_SDIN_MODE_FIELD ((RegisterField) {TMC4361A_SDIN_MODE_MASK, TMC4361A_SDIN_MODE_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_POL_DIR_IN_MASK 0x00000100 +#define TMC4361A_POL_DIR_IN_SHIFT 8 +#define TMC4361A_POL_DIR_IN_FIELD ((RegisterField) {TMC4361A_POL_DIR_IN_MASK, TMC4361A_POL_DIR_IN_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_SD_INDIRECT_CONTROL_MASK 0x00000200 +#define TMC4361A_SD_INDIRECT_CONTROL_SHIFT 9 +#define TMC4361A_SD_INDIRECT_CONTROL_FIELD ((RegisterField) {TMC4361A_SD_INDIRECT_CONTROL_MASK, TMC4361A_SD_INDIRECT_CONTROL_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_SERIAL_ENC_IN_MODE_MASK 0x00000c00 +#define TMC4361A_SERIAL_ENC_IN_MODE_SHIFT 10 +#define TMC4361A_SERIAL_ENC_IN_MODE_FIELD ((RegisterField) {TMC4361A_SERIAL_ENC_IN_MODE_MASK, TMC4361A_SERIAL_ENC_IN_MODE_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_DIFF_ENC_IN_DISABLE_MASK 0x00001000 +#define TMC4361A_DIFF_ENC_IN_DISABLE_SHIFT 12 +#define TMC4361A_DIFF_ENC_IN_DISABLE_FIELD ((RegisterField) {TMC4361A_DIFF_ENC_IN_DISABLE_MASK, TMC4361A_DIFF_ENC_IN_DISABLE_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_STDBY_CLK_PIN_ASSIGNMENT_MASK 0x00006000 +#define TMC4361A_STDBY_CLK_PIN_ASSIGNMENT_SHIFT 13 +#define TMC4361A_STDBY_CLK_PIN_ASSIGNMENT_FIELD ((RegisterField) {TMC4361A_STDBY_CLK_PIN_ASSIGNMENT_MASK, TMC4361A_STDBY_CLK_PIN_ASSIGNMENT_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_INTR_POL_MASK 0x00008000 +#define TMC4361A_INTR_POL_SHIFT 15 +#define TMC4361A_INTR_POL_FIELD ((RegisterField) {TMC4361A_INTR_POL_MASK, TMC4361A_INTR_POL_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_INVERT_POL_TARGET_REACHED_MASK 0x00010000 +#define TMC4361A_INVERT_POL_TARGET_REACHED_SHIFT 16 +#define TMC4361A_INVERT_POL_TARGET_REACHED_FIELD ((RegisterField) {TMC4361A_INVERT_POL_TARGET_REACHED_MASK, TMC4361A_INVERT_POL_TARGET_REACHED_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_FS_EN_MASK 0x00080000 +#define TMC4361A_FS_EN_SHIFT 19 +#define TMC4361A_FS_EN_FIELD ((RegisterField) {TMC4361A_FS_EN_MASK, TMC4361A_FS_EN_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_FS_SDOUT_MASK 0x00100000 +#define TMC4361A_FS_SDOUT_SHIFT 20 +#define TMC4361A_FS_SDOUT_FIELD ((RegisterField) {TMC4361A_FS_SDOUT_MASK, TMC4361A_FS_SDOUT_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_DCSTEP_MODE_MASK 0x00600000 +#define TMC4361A_DCSTEP_MODE_SHIFT 21 +#define TMC4361A_DCSTEP_MODE_FIELD ((RegisterField) {TMC4361A_DCSTEP_MODE_MASK, TMC4361A_DCSTEP_MODE_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_PWM_OUT_EN_MASK 0x00800000 +#define TMC4361A_PWM_OUT_EN_SHIFT 23 +#define TMC4361A_PWM_OUT_EN_FIELD ((RegisterField) {TMC4361A_PWM_OUT_EN_MASK, TMC4361A_PWM_OUT_EN_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_SERIAL_ENC_OUT_ENABLE_MASK 0x01000000 +#define TMC4361A_SERIAL_ENC_OUT_ENABLE_SHIFT 24 +#define TMC4361A_SERIAL_ENC_OUT_ENABLE_FIELD ((RegisterField) {TMC4361A_SERIAL_ENC_OUT_ENABLE_MASK, TMC4361A_SERIAL_ENC_OUT_ENABLE_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_SERIAL_ENC_OUT_DIFF_DISABLE_MASK 0x02000000 +#define TMC4361A_SERIAL_ENC_OUT_DIFF_DISABLE_SHIFT 25 +#define TMC4361A_SERIAL_ENC_OUT_DIFF_DISABLE_FIELD ((RegisterField) {TMC4361A_SERIAL_ENC_OUT_DIFF_DISABLE_MASK, TMC4361A_SERIAL_ENC_OUT_DIFF_DISABLE_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_AUTOMATIC_DIRECT_SDIN_SWITCH_OFF_MASK 0x04000000 +#define TMC4361A_AUTOMATIC_DIRECT_SDIN_SWITCH_OFF_SHIFT 26 +#define TMC4361A_AUTOMATIC_DIRECT_SDIN_SWITCH_OFF_FIELD ((RegisterField) {TMC4361A_AUTOMATIC_DIRECT_SDIN_SWITCH_OFF_MASK, TMC4361A_AUTOMATIC_DIRECT_SDIN_SWITCH_OFF_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_CIRCULAR_CNT_AS_XLATCH_MASK 0x08000000 +#define TMC4361A_CIRCULAR_CNT_AS_XLATCH_SHIFT 27 +#define TMC4361A_CIRCULAR_CNT_AS_XLATCH_FIELD ((RegisterField) {TMC4361A_CIRCULAR_CNT_AS_XLATCH_MASK, TMC4361A_CIRCULAR_CNT_AS_XLATCH_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_REVERSE_MOTOR_DIR_MASK 0x10000000 +#define TMC4361A_REVERSE_MOTOR_DIR_SHIFT 28 +#define TMC4361A_REVERSE_MOTOR_DIR_FIELD ((RegisterField) {TMC4361A_REVERSE_MOTOR_DIR_MASK, TMC4361A_REVERSE_MOTOR_DIR_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_INTR_TR_PU_PD_EN_MASK 0x20000000 +#define TMC4361A_INTR_TR_PU_PD_EN_SHIFT 29 +#define TMC4361A_INTR_TR_PU_PD_EN_FIELD ((RegisterField) {TMC4361A_INTR_TR_PU_PD_EN_MASK, TMC4361A_INTR_TR_PU_PD_EN_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_INTR_AS_WIRED_AND_MASK 0x40000000 +#define TMC4361A_INTR_AS_WIRED_AND_SHIFT 30 +#define TMC4361A_INTR_AS_WIRED_AND_FIELD ((RegisterField) {TMC4361A_INTR_AS_WIRED_AND_MASK, TMC4361A_INTR_AS_WIRED_AND_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_TR_AS_WIRED_AND_MASK 0x80000000 +#define TMC4361A_TR_AS_WIRED_AND_SHIFT 31 +#define TMC4361A_TR_AS_WIRED_AND_FIELD ((RegisterField) {TMC4361A_TR_AS_WIRED_AND_MASK, TMC4361A_TR_AS_WIRED_AND_SHIFT, TMC4361A_GENERAL_CONF, false}) +#define TMC4361A_STOP_LEFT_EN_MASK 0x00000001 +#define TMC4361A_STOP_LEFT_EN_SHIFT 0 +#define TMC4361A_STOP_LEFT_EN_FIELD ((RegisterField) {TMC4361A_STOP_LEFT_EN_MASK, TMC4361A_STOP_LEFT_EN_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_STOP_RIGHT_EN_MASK 0x00000002 +#define TMC4361A_STOP_RIGHT_EN_SHIFT 1 +#define TMC4361A_STOP_RIGHT_EN_FIELD ((RegisterField) {TMC4361A_STOP_RIGHT_EN_MASK, TMC4361A_STOP_RIGHT_EN_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_POL_STOP_LEFT_MASK 0x00000004 +#define TMC4361A_POL_STOP_LEFT_SHIFT 2 +#define TMC4361A_POL_STOP_LEFT_FIELD ((RegisterField) {TMC4361A_POL_STOP_LEFT_MASK, TMC4361A_POL_STOP_LEFT_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_POL_STOP_RIGHT_MASK 0x00000008 +#define TMC4361A_POL_STOP_RIGHT_SHIFT 3 +#define TMC4361A_POL_STOP_RIGHT_FIELD ((RegisterField) {TMC4361A_POL_STOP_RIGHT_MASK, TMC4361A_POL_STOP_RIGHT_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_INVERT_STOP_DIRECTION_MASK 0x00000010 +#define TMC4361A_INVERT_STOP_DIRECTION_SHIFT 4 +#define TMC4361A_INVERT_STOP_DIRECTION_FIELD ((RegisterField) {TMC4361A_INVERT_STOP_DIRECTION_MASK, TMC4361A_INVERT_STOP_DIRECTION_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_SOFT_STOP_EN_MASK 0x00000020 +#define TMC4361A_SOFT_STOP_EN_SHIFT 5 +#define TMC4361A_SOFT_STOP_EN_FIELD ((RegisterField) {TMC4361A_SOFT_STOP_EN_MASK, TMC4361A_SOFT_STOP_EN_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_VIRTUAL_LEFT_LIMIT_EN_MASK 0x00000040 +#define TMC4361A_VIRTUAL_LEFT_LIMIT_EN_SHIFT 6 +#define TMC4361A_VIRTUAL_LEFT_LIMIT_EN_FIELD ((RegisterField) {TMC4361A_VIRTUAL_LEFT_LIMIT_EN_MASK, TMC4361A_VIRTUAL_LEFT_LIMIT_EN_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_VIRTUAL_RIGHT_LIMIT_EN_MASK 0x00000080 +#define TMC4361A_VIRTUAL_RIGHT_LIMIT_EN_SHIFT 7 +#define TMC4361A_VIRTUAL_RIGHT_LIMIT_EN_FIELD ((RegisterField) {TMC4361A_VIRTUAL_RIGHT_LIMIT_EN_MASK, TMC4361A_VIRTUAL_RIGHT_LIMIT_EN_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_VIRT_STOP_MODE_MASK 0x00000300 +#define TMC4361A_VIRT_STOP_MODE_SHIFT 8 +#define TMC4361A_VIRT_STOP_MODE_FIELD ((RegisterField) {TMC4361A_VIRT_STOP_MODE_MASK, TMC4361A_VIRT_STOP_MODE_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_LATCH_X_ON_INACTIVE_L_MASK 0x00000400 +#define TMC4361A_LATCH_X_ON_INACTIVE_L_SHIFT 10 +#define TMC4361A_LATCH_X_ON_INACTIVE_L_FIELD ((RegisterField) {TMC4361A_LATCH_X_ON_INACTIVE_L_MASK, TMC4361A_LATCH_X_ON_INACTIVE_L_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_LATCH_X_ON_ACTIVE_L_MASK 0x00000800 +#define TMC4361A_LATCH_X_ON_ACTIVE_L_SHIFT 11 +#define TMC4361A_LATCH_X_ON_ACTIVE_L_FIELD ((RegisterField) {TMC4361A_LATCH_X_ON_ACTIVE_L_MASK, TMC4361A_LATCH_X_ON_ACTIVE_L_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_LATCH_X_ON_INACTIVE_R_MASK 0x00001000 +#define TMC4361A_LATCH_X_ON_INACTIVE_R_SHIFT 12 +#define TMC4361A_LATCH_X_ON_INACTIVE_R_FIELD ((RegisterField) {TMC4361A_LATCH_X_ON_INACTIVE_R_MASK, TMC4361A_LATCH_X_ON_INACTIVE_R_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_LATCH_X_ON_ACTIVE_R_MASK 0x00002000 +#define TMC4361A_LATCH_X_ON_ACTIVE_R_SHIFT 13 +#define TMC4361A_LATCH_X_ON_ACTIVE_R_FIELD ((RegisterField) {TMC4361A_LATCH_X_ON_ACTIVE_R_MASK, TMC4361A_LATCH_X_ON_ACTIVE_R_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_STOP_LEFT_IS_HOME_MASK 0x00004000 +#define TMC4361A_STOP_LEFT_IS_HOME_SHIFT 14 +#define TMC4361A_STOP_LEFT_IS_HOME_FIELD ((RegisterField) {TMC4361A_STOP_LEFT_IS_HOME_MASK, TMC4361A_STOP_LEFT_IS_HOME_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_HOME_EVENT_MASK 0x000f0000 +#define TMC4361A_HOME_EVENT_SHIFT 16 +#define TMC4361A_HOME_EVENT_FIELD ((RegisterField) {TMC4361A_HOME_EVENT_MASK, TMC4361A_HOME_EVENT_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_START_HOME_TRACKING_MASK 0x00100000 +#define TMC4361A_START_HOME_TRACKING_SHIFT 20 +#define TMC4361A_START_HOME_TRACKING_FIELD ((RegisterField) {TMC4361A_START_HOME_TRACKING_MASK, TMC4361A_START_HOME_TRACKING_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_CLR_POS_AT_TARGET_MASK 0x00200000 +#define TMC4361A_CLR_POS_AT_TARGET_SHIFT 21 +#define TMC4361A_CLR_POS_AT_TARGET_FIELD ((RegisterField) {TMC4361A_CLR_POS_AT_TARGET_MASK, TMC4361A_CLR_POS_AT_TARGET_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_CIRCULAR_MOVEMENT_EN_MASK 0x00400000 +#define TMC4361A_CIRCULAR_MOVEMENT_EN_SHIFT 22 +#define TMC4361A_CIRCULAR_MOVEMENT_EN_FIELD ((RegisterField) {TMC4361A_CIRCULAR_MOVEMENT_EN_MASK, TMC4361A_CIRCULAR_MOVEMENT_EN_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_POS_COMP_OUTPUT_MASK 0x01800000 +#define TMC4361A_POS_COMP_OUTPUT_SHIFT 23 +#define TMC4361A_POS_COMP_OUTPUT_FIELD ((RegisterField) {TMC4361A_POS_COMP_OUTPUT_MASK, TMC4361A_POS_COMP_OUTPUT_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_POS_COMP_SOURCE_MASK 0x02000000 +#define TMC4361A_POS_COMP_SOURCE_SHIFT 25 +#define TMC4361A_POS_COMP_SOURCE_FIELD ((RegisterField) {TMC4361A_POS_COMP_SOURCE_MASK, TMC4361A_POS_COMP_SOURCE_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_STOP_ON_STALL_MASK 0x04000000 +#define TMC4361A_STOP_ON_STALL_SHIFT 26 +#define TMC4361A_STOP_ON_STALL_FIELD ((RegisterField) {TMC4361A_STOP_ON_STALL_MASK, TMC4361A_STOP_ON_STALL_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_DRV_AFTER_STALL_MASK 0x08000000 +#define TMC4361A_DRV_AFTER_STALL_SHIFT 27 +#define TMC4361A_DRV_AFTER_STALL_FIELD ((RegisterField) {TMC4361A_DRV_AFTER_STALL_MASK, TMC4361A_DRV_AFTER_STALL_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_MODIFIED_POS_COPARE_MASK 0x30000000 +#define TMC4361A_MODIFIED_POS_COPARE_SHIFT 28 +#define TMC4361A_MODIFIED_POS_COPARE_FIELD ((RegisterField) {TMC4361A_MODIFIED_POS_COPARE_MASK, TMC4361A_MODIFIED_POS_COPARE_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_AUTOMATIC_COVER_MASK 0x40000000 +#define TMC4361A_AUTOMATIC_COVER_SHIFT 30 +#define TMC4361A_AUTOMATIC_COVER_FIELD ((RegisterField) {TMC4361A_AUTOMATIC_COVER_MASK, TMC4361A_AUTOMATIC_COVER_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_CIRCULAR_ENC_EN_MASK 0x80000000 +#define TMC4361A_CIRCULAR_ENC_EN_SHIFT 31 +#define TMC4361A_CIRCULAR_ENC_EN_FIELD ((RegisterField) {TMC4361A_CIRCULAR_ENC_EN_MASK, TMC4361A_CIRCULAR_ENC_EN_SHIFT, TMC4361A_REFERENCE_CONF, false}) +#define TMC4361A_START_EN_0__MASK 0x00000001 +#define TMC4361A_START_EN_0__SHIFT 0 +#define TMC4361A_START_EN_0__FIELD ((RegisterField) {TMC4361A_START_EN_0__MASK, TMC4361A_START_EN_0__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_START_EN_1__MASK 0x00000002 +#define TMC4361A_START_EN_1__SHIFT 1 +#define TMC4361A_START_EN_1__FIELD ((RegisterField) {TMC4361A_START_EN_1__MASK, TMC4361A_START_EN_1__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_START_EN_2__MASK 0x00000004 +#define TMC4361A_START_EN_2__SHIFT 2 +#define TMC4361A_START_EN_2__FIELD ((RegisterField) {TMC4361A_START_EN_2__MASK, TMC4361A_START_EN_2__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_START_EN_3__MASK 0x00000008 +#define TMC4361A_START_EN_3__SHIFT 3 +#define TMC4361A_START_EN_3__FIELD ((RegisterField) {TMC4361A_START_EN_3__MASK, TMC4361A_START_EN_3__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_START_EN_4__MASK 0x00000010 +#define TMC4361A_START_EN_4__SHIFT 4 +#define TMC4361A_START_EN_4__FIELD ((RegisterField) {TMC4361A_START_EN_4__MASK, TMC4361A_START_EN_4__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_TRIGGER_EVENTS_0__MASK 0x00000020 +#define TMC4361A_TRIGGER_EVENTS_0__SHIFT 5 +#define TMC4361A_TRIGGER_EVENTS_0__FIELD ((RegisterField) {TMC4361A_TRIGGER_EVENTS_0__MASK, TMC4361A_TRIGGER_EVENTS_0__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_TRIGGER_EVENTS_1__MASK 0x00000040 +#define TMC4361A_TRIGGER_EVENTS_1__SHIFT 6 +#define TMC4361A_TRIGGER_EVENTS_1__FIELD ((RegisterField) {TMC4361A_TRIGGER_EVENTS_1__MASK, TMC4361A_TRIGGER_EVENTS_1__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_TRIGGER_EVENTS_2__MASK 0x00000080 +#define TMC4361A_TRIGGER_EVENTS_2__SHIFT 7 +#define TMC4361A_TRIGGER_EVENTS_2__FIELD ((RegisterField) {TMC4361A_TRIGGER_EVENTS_2__MASK, TMC4361A_TRIGGER_EVENTS_2__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_TRIGGER_EVENTS_3__MASK 0x00000100 +#define TMC4361A_TRIGGER_EVENTS_3__SHIFT 8 +#define TMC4361A_TRIGGER_EVENTS_3__FIELD ((RegisterField) {TMC4361A_TRIGGER_EVENTS_3__MASK, TMC4361A_TRIGGER_EVENTS_3__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_POL_START_SIGNAL_MASK 0x00000200 +#define TMC4361A_POL_START_SIGNAL_SHIFT 9 +#define TMC4361A_POL_START_SIGNAL_FIELD ((RegisterField) {TMC4361A_POL_START_SIGNAL_MASK, TMC4361A_POL_START_SIGNAL_SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_IMMEDIATE_START_IN_MASK 0x00000400 +#define TMC4361A_IMMEDIATE_START_IN_SHIFT 10 +#define TMC4361A_IMMEDIATE_START_IN_FIELD ((RegisterField) {TMC4361A_IMMEDIATE_START_IN_MASK, TMC4361A_IMMEDIATE_START_IN_SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_BUSY_STATE_EN_MASK 0x00000800 +#define TMC4361A_BUSY_STATE_EN_SHIFT 11 +#define TMC4361A_BUSY_STATE_EN_FIELD ((RegisterField) {TMC4361A_BUSY_STATE_EN_MASK, TMC4361A_BUSY_STATE_EN_SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_PIPELINE_EN_0__MASK 0x00001000 +#define TMC4361A_PIPELINE_EN_0__SHIFT 12 +#define TMC4361A_PIPELINE_EN_0__FIELD ((RegisterField) {TMC4361A_PIPELINE_EN_0__MASK, TMC4361A_PIPELINE_EN_0__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_PIPELINE_EN_1__MASK 0x00002000 +#define TMC4361A_PIPELINE_EN_1__SHIFT 13 +#define TMC4361A_PIPELINE_EN_1__FIELD ((RegisterField) {TMC4361A_PIPELINE_EN_1__MASK, TMC4361A_PIPELINE_EN_1__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_PIPELINE_EN_2__MASK 0x00004000 +#define TMC4361A_PIPELINE_EN_2__SHIFT 14 +#define TMC4361A_PIPELINE_EN_2__FIELD ((RegisterField) {TMC4361A_PIPELINE_EN_2__MASK, TMC4361A_PIPELINE_EN_2__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_PIPELINE_EN_3__MASK 0x00008000 +#define TMC4361A_PIPELINE_EN_3__SHIFT 15 +#define TMC4361A_PIPELINE_EN_3__FIELD ((RegisterField) {TMC4361A_PIPELINE_EN_3__MASK, TMC4361A_PIPELINE_EN_3__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_SHADOW_OPTION_MASK 0x00030000 +#define TMC4361A_SHADOW_OPTION_SHIFT 16 +#define TMC4361A_SHADOW_OPTION_FIELD ((RegisterField) {TMC4361A_SHADOW_OPTION_MASK, TMC4361A_SHADOW_OPTION_SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_CYCLIC_SHADOW_REGS_MASK 0x00040000 +#define TMC4361A_CYCLIC_SHADOW_REGS_SHIFT 18 +#define TMC4361A_CYCLIC_SHADOW_REGS_FIELD ((RegisterField) {TMC4361A_CYCLIC_SHADOW_REGS_MASK, TMC4361A_CYCLIC_SHADOW_REGS_SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_SHADOW_MISS_CNT_MASK 0x00f00000 +#define TMC4361A_SHADOW_MISS_CNT_SHIFT 20 +#define TMC4361A_SHADOW_MISS_CNT_FIELD ((RegisterField) {TMC4361A_SHADOW_MISS_CNT_MASK, TMC4361A_SHADOW_MISS_CNT_SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_XPIPE_REWRITE_REG_0__MASK 0x01000000 +#define TMC4361A_XPIPE_REWRITE_REG_0__SHIFT 24 +#define TMC4361A_XPIPE_REWRITE_REG_0__FIELD ((RegisterField) {TMC4361A_XPIPE_REWRITE_REG_0__MASK, TMC4361A_XPIPE_REWRITE_REG_0__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_XPIPE_REWRITE_REG_1__MASK 0x02000000 +#define TMC4361A_XPIPE_REWRITE_REG_1__SHIFT 25 +#define TMC4361A_XPIPE_REWRITE_REG_1__FIELD ((RegisterField) {TMC4361A_XPIPE_REWRITE_REG_1__MASK, TMC4361A_XPIPE_REWRITE_REG_1__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_XPIPE_REWRITE_REG_2__MASK 0x04000000 +#define TMC4361A_XPIPE_REWRITE_REG_2__SHIFT 26 +#define TMC4361A_XPIPE_REWRITE_REG_2__FIELD ((RegisterField) {TMC4361A_XPIPE_REWRITE_REG_2__MASK, TMC4361A_XPIPE_REWRITE_REG_2__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_XPIPE_REWRITE_REG_3__MASK 0x08000000 +#define TMC4361A_XPIPE_REWRITE_REG_3__SHIFT 27 +#define TMC4361A_XPIPE_REWRITE_REG_3__FIELD ((RegisterField) {TMC4361A_XPIPE_REWRITE_REG_3__MASK, TMC4361A_XPIPE_REWRITE_REG_3__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_XPIPE_REWRITE_REG_4__MASK 0x10000000 +#define TMC4361A_XPIPE_REWRITE_REG_4__SHIFT 28 +#define TMC4361A_XPIPE_REWRITE_REG_4__FIELD ((RegisterField) {TMC4361A_XPIPE_REWRITE_REG_4__MASK, TMC4361A_XPIPE_REWRITE_REG_4__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_XPIPE_REWRITE_REG_5__MASK 0x20000000 +#define TMC4361A_XPIPE_REWRITE_REG_5__SHIFT 29 +#define TMC4361A_XPIPE_REWRITE_REG_5__FIELD ((RegisterField) {TMC4361A_XPIPE_REWRITE_REG_5__MASK, TMC4361A_XPIPE_REWRITE_REG_5__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_XPIPE_REWRITE_REG_6__MASK 0x40000000 +#define TMC4361A_XPIPE_REWRITE_REG_6__SHIFT 30 +#define TMC4361A_XPIPE_REWRITE_REG_6__FIELD ((RegisterField) {TMC4361A_XPIPE_REWRITE_REG_6__MASK, TMC4361A_XPIPE_REWRITE_REG_6__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_XPIPE_REWRITE_REG_7__MASK 0x80000000 +#define TMC4361A_XPIPE_REWRITE_REG_7__SHIFT 31 +#define TMC4361A_XPIPE_REWRITE_REG_7__FIELD ((RegisterField) {TMC4361A_XPIPE_REWRITE_REG_7__MASK, TMC4361A_XPIPE_REWRITE_REG_7__SHIFT, TMC4361A_START_CONF, false}) +#define TMC4361A_SR_ENC_IN_MASK 0x00000007 +#define TMC4361A_SR_ENC_IN_SHIFT 0 +#define TMC4361A_SR_ENC_IN_FIELD ((RegisterField) {TMC4361A_SR_ENC_IN_MASK, TMC4361A_SR_ENC_IN_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_FILT_L_ENC_IN_MASK 0x00000070 +#define TMC4361A_FILT_L_ENC_IN_SHIFT 4 +#define TMC4361A_FILT_L_ENC_IN_FIELD ((RegisterField) {TMC4361A_FILT_L_ENC_IN_MASK, TMC4361A_FILT_L_ENC_IN_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_SD_FILT0_MASK 0x00000080 +#define TMC4361A_SD_FILT0_SHIFT 7 +#define TMC4361A_SD_FILT0_FIELD ((RegisterField) {TMC4361A_SD_FILT0_MASK, TMC4361A_SD_FILT0_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_SR_REF_MASK 0x00000700 +#define TMC4361A_SR_REF_SHIFT 8 +#define TMC4361A_SR_REF_FIELD ((RegisterField) {TMC4361A_SR_REF_MASK, TMC4361A_SR_REF_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_FILT_L_REF_MASK 0x00007000 +#define TMC4361A_FILT_L_REF_SHIFT 12 +#define TMC4361A_FILT_L_REF_FIELD ((RegisterField) {TMC4361A_FILT_L_REF_MASK, TMC4361A_FILT_L_REF_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_SD_FILT1_MASK 0x00008000 +#define TMC4361A_SD_FILT1_SHIFT 15 +#define TMC4361A_SD_FILT1_FIELD ((RegisterField) {TMC4361A_SD_FILT1_MASK, TMC4361A_SD_FILT1_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_SR_S_MASK 0x00070000 +#define TMC4361A_SR_S_SHIFT 16 +#define TMC4361A_SR_S_FIELD ((RegisterField) {TMC4361A_SR_S_MASK, TMC4361A_SR_S_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_FILT_L_S_MASK 0x00700000 +#define TMC4361A_FILT_L_S_SHIFT 20 +#define TMC4361A_FILT_L_S_FIELD ((RegisterField) {TMC4361A_FILT_L_S_MASK, TMC4361A_FILT_L_S_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_SD_FILT2_MASK 0x00800000 +#define TMC4361A_SD_FILT2_SHIFT 23 +#define TMC4361A_SD_FILT2_FIELD ((RegisterField) {TMC4361A_SD_FILT2_MASK, TMC4361A_SD_FILT2_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_SR_ENC_OUT_MASK 0x07000000 +#define TMC4361A_SR_ENC_OUT_SHIFT 24 +#define TMC4361A_SR_ENC_OUT_FIELD ((RegisterField) {TMC4361A_SR_ENC_OUT_MASK, TMC4361A_SR_ENC_OUT_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_FILT_L_ENC_OUT_MASK 0x70000000 +#define TMC4361A_FILT_L_ENC_OUT_SHIFT 28 +#define TMC4361A_FILT_L_ENC_OUT_FIELD ((RegisterField) {TMC4361A_FILT_L_ENC_OUT_MASK, TMC4361A_FILT_L_ENC_OUT_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_SD_FILT3_MASK 0x80000000 +#define TMC4361A_SD_FILT3_SHIFT 31 +#define TMC4361A_SD_FILT3_FIELD ((RegisterField) {TMC4361A_SD_FILT3_MASK, TMC4361A_SD_FILT3_SHIFT, TMC4361A_INPUT_FILT_CONF, false}) +#define TMC4361A_SPI_OUTPUT_FORMAT_MASK 0x0000000f +#define TMC4361A_SPI_OUTPUT_FORMAT_SHIFT 0 +#define TMC4361A_SPI_OUTPUT_FORMAT_FIELD ((RegisterField) {TMC4361A_SPI_OUTPUT_FORMAT_MASK, TMC4361A_SPI_OUTPUT_FORMAT_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_SSI_OUT_MTIME_MASK 0x00fffff0 +#define TMC4361A_SSI_OUT_MTIME_SHIFT 4 +#define TMC4361A_SSI_OUT_MTIME_FIELD ((RegisterField) {TMC4361A_SSI_OUT_MTIME_MASK, TMC4361A_SSI_OUT_MTIME_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_MIXED_DECAY_MASK 0x00000030 +#define TMC4361A_MIXED_DECAY_SHIFT 4 +#define TMC4361A_MIXED_DECAY_FIELD ((RegisterField) {TMC4361A_MIXED_DECAY_MASK, TMC4361A_MIXED_DECAY_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_AUTO_DOUBLE_CHOPSYNC_MASK 0x00001000 +#define TMC4361A_AUTO_DOUBLE_CHOPSYNC_SHIFT 12 +#define TMC4361A_AUTO_DOUBLE_CHOPSYNC_FIELD ((RegisterField) {TMC4361A_AUTO_DOUBLE_CHOPSYNC_MASK, TMC4361A_AUTO_DOUBLE_CHOPSYNC_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_STDBY_ON_STALL_FOR_24X_MASK 0x00000040 +#define TMC4361A_STDBY_ON_STALL_FOR_24X_SHIFT 6 +#define TMC4361A_STDBY_ON_STALL_FOR_24X_FIELD ((RegisterField) {TMC4361A_STDBY_ON_STALL_FOR_24X_MASK, TMC4361A_STDBY_ON_STALL_FOR_24X_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_STALL_FLAG_INSTEAD_OF_UV_EN_MASK 0x00000080 +#define TMC4361A_STALL_FLAG_INSTEAD_OF_UV_EN_SHIFT 7 +#define TMC4361A_STALL_FLAG_INSTEAD_OF_UV_EN_FIELD ((RegisterField) {TMC4361A_STALL_FLAG_INSTEAD_OF_UV_EN_MASK, TMC4361A_STALL_FLAG_INSTEAD_OF_UV_EN_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_STALL_LOAD_LIMIT_MASK 0x00000700 +#define TMC4361A_STALL_LOAD_LIMIT_SHIFT 8 +#define TMC4361A_STALL_LOAD_LIMIT_FIELD ((RegisterField) {TMC4361A_STALL_LOAD_LIMIT_MASK, TMC4361A_STALL_LOAD_LIMIT_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_PWM_PHASE_SHFT_EN_MASK 0x00000800 +#define TMC4361A_PWM_PHASE_SHFT_EN_SHIFT 11 +#define TMC4361A_PWM_PHASE_SHFT_EN_FIELD ((RegisterField) {TMC4361A_PWM_PHASE_SHFT_EN_MASK, TMC4361A_PWM_PHASE_SHFT_EN_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_THREE_PHASE_STEPPER_EN_MASK 0x00000010 +#define TMC4361A_THREE_PHASE_STEPPER_EN_SHIFT 4 +#define TMC4361A_THREE_PHASE_STEPPER_EN_FIELD ((RegisterField) {TMC4361A_THREE_PHASE_STEPPER_EN_MASK, TMC4361A_THREE_PHASE_STEPPER_EN_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_AUTOREPEAT_COVER_EN_MASK 0x00000080 +#define TMC4361A_AUTOREPEAT_COVER_EN_SHIFT 7 +#define TMC4361A_AUTOREPEAT_COVER_EN_FIELD ((RegisterField) {TMC4361A_AUTOREPEAT_COVER_EN_MASK, TMC4361A_AUTOREPEAT_COVER_EN_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_COVER_DONE_ONLY_FOR_COVER_MASK 0x00001000 +#define TMC4361A_COVER_DONE_ONLY_FOR_COVER_SHIFT 12 +#define TMC4361A_COVER_DONE_ONLY_FOR_COVER_FIELD ((RegisterField) {TMC4361A_COVER_DONE_ONLY_FOR_COVER_MASK, TMC4361A_COVER_DONE_ONLY_FOR_COVER_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_SCALE_VALE_TRANSFER_EN_MASK 0x00000020 +#define TMC4361A_SCALE_VALE_TRANSFER_EN_SHIFT 5 +#define TMC4361A_SCALE_VALE_TRANSFER_EN_FIELD ((RegisterField) {TMC4361A_SCALE_VALE_TRANSFER_EN_MASK, TMC4361A_SCALE_VALE_TRANSFER_EN_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_DISABLE_POLLING_MASK 0x00000040 +#define TMC4361A_DISABLE_POLLING_SHIFT 6 +#define TMC4361A_DISABLE_POLLING_FIELD ((RegisterField) {TMC4361A_DISABLE_POLLING_MASK, TMC4361A_DISABLE_POLLING_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_POLL_BLOCK_EXP_MASK 0x00000f00 +#define TMC4361A_POLL_BLOCK_EXP_SHIFT 8 +#define TMC4361A_POLL_BLOCK_EXP_FIELD ((RegisterField) {TMC4361A_POLL_BLOCK_EXP_MASK, TMC4361A_POLL_BLOCK_EXP_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_SCK_LOW_BEFORE_CSN_MASK 0x00000010 +#define TMC4361A_SCK_LOW_BEFORE_CSN_SHIFT 4 +#define TMC4361A_SCK_LOW_BEFORE_CSN_FIELD ((RegisterField) {TMC4361A_SCK_LOW_BEFORE_CSN_MASK, TMC4361A_SCK_LOW_BEFORE_CSN_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_NEW_OUT_BIT_AT_RISE_MASK 0x00000020 +#define TMC4361A_NEW_OUT_BIT_AT_RISE_SHIFT 5 +#define TMC4361A_NEW_OUT_BIT_AT_RISE_FIELD ((RegisterField) {TMC4361A_NEW_OUT_BIT_AT_RISE_MASK, TMC4361A_NEW_OUT_BIT_AT_RISE_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_DAC_CMD_LENGTH_MASK 0x00000f80 +#define TMC4361A_DAC_CMD_LENGTH_SHIFT 7 +#define TMC4361A_DAC_CMD_LENGTH_FIELD ((RegisterField) {TMC4361A_DAC_CMD_LENGTH_MASK, TMC4361A_DAC_CMD_LENGTH_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_COVER_DATA_LENGTH_MASK 0x000fe000 +#define TMC4361A_COVER_DATA_LENGTH_SHIFT 13 +#define TMC4361A_COVER_DATA_LENGTH_FIELD ((RegisterField) {TMC4361A_COVER_DATA_LENGTH_MASK, TMC4361A_COVER_DATA_LENGTH_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_SPI_OUT_LOW_TIME_MASK 0x00f00000 +#define TMC4361A_SPI_OUT_LOW_TIME_SHIFT 20 +#define TMC4361A_SPI_OUT_LOW_TIME_FIELD ((RegisterField) {TMC4361A_SPI_OUT_LOW_TIME_MASK, TMC4361A_SPI_OUT_LOW_TIME_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_SPI_OUT_HIGH_TIME_MASK 0x0f000000 +#define TMC4361A_SPI_OUT_HIGH_TIME_SHIFT 24 +#define TMC4361A_SPI_OUT_HIGH_TIME_FIELD ((RegisterField) {TMC4361A_SPI_OUT_HIGH_TIME_MASK, TMC4361A_SPI_OUT_HIGH_TIME_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_SPI_OUT_BLOCK_TIME_MASK 0xf0000000 +#define TMC4361A_SPI_OUT_BLOCK_TIME_SHIFT 28 +#define TMC4361A_SPI_OUT_BLOCK_TIME_FIELD ((RegisterField) {TMC4361A_SPI_OUT_BLOCK_TIME_MASK, TMC4361A_SPI_OUT_BLOCK_TIME_SHIFT, TMC4361A_SPI_OUT_CONF, false}) +#define TMC4361A_HOLD_CURRENT_SCALE_EN_MASK 0x00000001 +#define TMC4361A_HOLD_CURRENT_SCALE_EN_SHIFT 0 +#define TMC4361A_HOLD_CURRENT_SCALE_EN_FIELD ((RegisterField) {TMC4361A_HOLD_CURRENT_SCALE_EN_MASK, TMC4361A_HOLD_CURRENT_SCALE_EN_SHIFT, TMC4361A_CURRENT_CONF, false}) +#define TMC4361A_DRIVE_CURRENT_SCALE_EN_MASK 0x00000002 +#define TMC4361A_DRIVE_CURRENT_SCALE_EN_SHIFT 1 +#define TMC4361A_DRIVE_CURRENT_SCALE_EN_FIELD ((RegisterField) {TMC4361A_DRIVE_CURRENT_SCALE_EN_MASK, TMC4361A_DRIVE_CURRENT_SCALE_EN_SHIFT, TMC4361A_CURRENT_CONF, false}) +#define TMC4361A_BOOST_CURRENT_ON_ACC_EN_MASK 0x00000004 +#define TMC4361A_BOOST_CURRENT_ON_ACC_EN_SHIFT 2 +#define TMC4361A_BOOST_CURRENT_ON_ACC_EN_FIELD ((RegisterField) {TMC4361A_BOOST_CURRENT_ON_ACC_EN_MASK, TMC4361A_BOOST_CURRENT_ON_ACC_EN_SHIFT, TMC4361A_CURRENT_CONF, false}) +#define TMC4361A_BOOST_CURRENT_ON_DEC_EN_MASK 0x00000008 +#define TMC4361A_BOOST_CURRENT_ON_DEC_EN_SHIFT 3 +#define TMC4361A_BOOST_CURRENT_ON_DEC_EN_FIELD ((RegisterField) {TMC4361A_BOOST_CURRENT_ON_DEC_EN_MASK, TMC4361A_BOOST_CURRENT_ON_DEC_EN_SHIFT, TMC4361A_CURRENT_CONF, false}) +#define TMC4361A_BOOST_CURRENT_AFTER_START_EN_MASK 0x00000010 +#define TMC4361A_BOOST_CURRENT_AFTER_START_EN_SHIFT 4 +#define TMC4361A_BOOST_CURRENT_AFTER_START_EN_FIELD ((RegisterField) {TMC4361A_BOOST_CURRENT_AFTER_START_EN_MASK, TMC4361A_BOOST_CURRENT_AFTER_START_EN_SHIFT, TMC4361A_CURRENT_CONF, false}) +#define TMC4361A_SEC_DRIVE_CURRENT_SCALE_EN_MASK 0x00000020 +#define TMC4361A_SEC_DRIVE_CURRENT_SCALE_EN_SHIFT 5 +#define TMC4361A_SEC_DRIVE_CURRENT_SCALE_EN_FIELD ((RegisterField) {TMC4361A_SEC_DRIVE_CURRENT_SCALE_EN_MASK, TMC4361A_SEC_DRIVE_CURRENT_SCALE_EN_SHIFT, TMC4361A_CURRENT_CONF, false}) +#define TMC4361A_FREEWHEELING_EN_MASK 0x00000040 +#define TMC4361A_FREEWHEELING_EN_SHIFT 6 +#define TMC4361A_FREEWHEELING_EN_FIELD ((RegisterField) {TMC4361A_FREEWHEELING_EN_MASK, TMC4361A_FREEWHEELING_EN_SHIFT, TMC4361A_CURRENT_CONF, false}) +#define TMC4361A_CLOSED_LOOP_SCALE_EN_MASK 0x00000080 +#define TMC4361A_CLOSED_LOOP_SCALE_EN_SHIFT 7 +#define TMC4361A_CLOSED_LOOP_SCALE_EN_FIELD ((RegisterField) {TMC4361A_CLOSED_LOOP_SCALE_EN_MASK, TMC4361A_CLOSED_LOOP_SCALE_EN_SHIFT, TMC4361A_CURRENT_CONF, false}) +#define TMC4361A_PWM_SCALE_EN_MASK 0x00000100 +#define TMC4361A_PWM_SCALE_EN_SHIFT 8 +#define TMC4361A_PWM_SCALE_EN_FIELD ((RegisterField) {TMC4361A_PWM_SCALE_EN_MASK, TMC4361A_PWM_SCALE_EN_SHIFT, TMC4361A_CURRENT_CONF, false}) +#define TMC4361A_PWM_AMPL_MASK 0xffff0000 +#define TMC4361A_PWM_AMPL_SHIFT 16 +#define TMC4361A_PWM_AMPL_FIELD ((RegisterField) {TMC4361A_PWM_AMPL_MASK, TMC4361A_PWM_AMPL_SHIFT, TMC4361A_CURRENT_CONF, false}) +#define TMC4361A_BOOST_SCALE_VAL_MASK 0x000000ff +#define TMC4361A_BOOST_SCALE_VAL_SHIFT 0 +#define TMC4361A_BOOST_SCALE_VAL_FIELD ((RegisterField) {TMC4361A_BOOST_SCALE_VAL_MASK, TMC4361A_BOOST_SCALE_VAL_SHIFT, TMC4361A_SCALE_VALUES, false}) +#define TMC4361A_DRV1_SCALE_VAL_MASK 0x0000ff00 +#define TMC4361A_DRV1_SCALE_VAL_SHIFT 8 +#define TMC4361A_DRV1_SCALE_VAL_FIELD ((RegisterField) {TMC4361A_DRV1_SCALE_VAL_MASK, TMC4361A_DRV1_SCALE_VAL_SHIFT, TMC4361A_SCALE_VALUES, false}) +#define TMC4361A_DRV2_SCALE_VAL_MASK 0x00ff0000 +#define TMC4361A_DRV2_SCALE_VAL_SHIFT 16 +#define TMC4361A_DRV2_SCALE_VAL_FIELD ((RegisterField) {TMC4361A_DRV2_SCALE_VAL_MASK, TMC4361A_DRV2_SCALE_VAL_SHIFT, TMC4361A_SCALE_VALUES, false}) +#define TMC4361A_HOLD_SCALE_VAL_MASK 0xff000000 +#define TMC4361A_HOLD_SCALE_VAL_SHIFT 24 +#define TMC4361A_HOLD_SCALE_VAL_FIELD ((RegisterField) {TMC4361A_HOLD_SCALE_VAL_MASK, TMC4361A_HOLD_SCALE_VAL_SHIFT, TMC4361A_SCALE_VALUES, false}) +#define TMC4361A_CL_IMIN_MASK 0x000000ff +#define TMC4361A_CL_IMIN_SHIFT 0 +#define TMC4361A_CL_IMIN_FIELD ((RegisterField) {TMC4361A_CL_IMIN_MASK, TMC4361A_CL_IMIN_SHIFT, TMC4361A_SCALE_VALUES, false}) +#define TMC4361A_CL_IMAX_MASK 0x0000ff00 +#define TMC4361A_CL_IMAX_SHIFT 8 +#define TMC4361A_CL_IMAX_FIELD ((RegisterField) {TMC4361A_CL_IMAX_MASK, TMC4361A_CL_IMAX_SHIFT, TMC4361A_SCALE_VALUES, false}) +#define TMC4361A_CL_START_UP_MASK 0x00ff0000 +#define TMC4361A_CL_START_UP_SHIFT 16 +#define TMC4361A_CL_START_UP_FIELD ((RegisterField) {TMC4361A_CL_START_UP_MASK, TMC4361A_CL_START_UP_SHIFT, TMC4361A_SCALE_VALUES, false}) +#define TMC4361A_CL_START_DN_MASK 0xff000000 +#define TMC4361A_CL_START_DN_SHIFT 24 +#define TMC4361A_CL_START_DN_FIELD ((RegisterField) {TMC4361A_CL_START_DN_MASK, TMC4361A_CL_START_DN_SHIFT, TMC4361A_SCALE_VALUES, false}) +#define TMC4361A_ENC_SEL_DECIMAL_MASK 0x00000001 +#define TMC4361A_ENC_SEL_DECIMAL_SHIFT 0 +#define TMC4361A_ENC_SEL_DECIMAL_FIELD ((RegisterField) {TMC4361A_ENC_SEL_DECIMAL_MASK, TMC4361A_ENC_SEL_DECIMAL_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_CLEAR_ON_N_MASK 0x00000002 +#define TMC4361A_CLEAR_ON_N_SHIFT 1 +#define TMC4361A_CLEAR_ON_N_FIELD ((RegisterField) {TMC4361A_CLEAR_ON_N_MASK, TMC4361A_CLEAR_ON_N_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_CLR_LATCH_CONT_ON_N_MASK 0x00000004 +#define TMC4361A_CLR_LATCH_CONT_ON_N_SHIFT 2 +#define TMC4361A_CLR_LATCH_CONT_ON_N_FIELD ((RegisterField) {TMC4361A_CLR_LATCH_CONT_ON_N_MASK, TMC4361A_CLR_LATCH_CONT_ON_N_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_CLR_LATCH_ONCE_ON_N_MASK 0x00000008 +#define TMC4361A_CLR_LATCH_ONCE_ON_N_SHIFT 3 +#define TMC4361A_CLR_LATCH_ONCE_ON_N_FIELD ((RegisterField) {TMC4361A_CLR_LATCH_ONCE_ON_N_MASK, TMC4361A_CLR_LATCH_ONCE_ON_N_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_POL_N_MASK 0x00000010 +#define TMC4361A_POL_N_SHIFT 4 +#define TMC4361A_POL_N_FIELD ((RegisterField) {TMC4361A_POL_N_MASK, TMC4361A_POL_N_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_N_CHAN_SENSITIVITY_MASK 0x00000060 +#define TMC4361A_N_CHAN_SENSITIVITY_SHIFT 5 +#define TMC4361A_N_CHAN_SENSITIVITY_FIELD ((RegisterField) {TMC4361A_N_CHAN_SENSITIVITY_MASK, TMC4361A_N_CHAN_SENSITIVITY_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_POL_A_FOR_N_MASK 0x00000080 +#define TMC4361A_POL_A_FOR_N_SHIFT 7 +#define TMC4361A_POL_A_FOR_N_FIELD ((RegisterField) {TMC4361A_POL_A_FOR_N_MASK, TMC4361A_POL_A_FOR_N_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_POL_B_FOR_N_MASK 0x00000100 +#define TMC4361A_POL_B_FOR_N_SHIFT 8 +#define TMC4361A_POL_B_FOR_N_FIELD ((RegisterField) {TMC4361A_POL_B_FOR_N_MASK, TMC4361A_POL_B_FOR_N_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_IGNORE_AB_MASK 0x00000200 +#define TMC4361A_IGNORE_AB_SHIFT 9 +#define TMC4361A_IGNORE_AB_FIELD ((RegisterField) {TMC4361A_IGNORE_AB_MASK, TMC4361A_IGNORE_AB_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_LATCH_ENC_ON_N_MASK 0x00000400 +#define TMC4361A_LATCH_ENC_ON_N_SHIFT 10 +#define TMC4361A_LATCH_ENC_ON_N_FIELD ((RegisterField) {TMC4361A_LATCH_ENC_ON_N_MASK, TMC4361A_LATCH_ENC_ON_N_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_LATCH_X_ON_N_MASK 0x00000800 +#define TMC4361A_LATCH_X_ON_N_SHIFT 11 +#define TMC4361A_LATCH_X_ON_N_FIELD ((RegisterField) {TMC4361A_LATCH_X_ON_N_MASK, TMC4361A_LATCH_X_ON_N_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_MULTI_TURN_IN_EN_MASK 0x00001000 +#define TMC4361A_MULTI_TURN_IN_EN_SHIFT 12 +#define TMC4361A_MULTI_TURN_IN_EN_FIELD ((RegisterField) {TMC4361A_MULTI_TURN_IN_EN_MASK, TMC4361A_MULTI_TURN_IN_EN_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_MULTI_TURN_IN_SIGNED_MASK 0x00002000 +#define TMC4361A_MULTI_TURN_IN_SIGNED_SHIFT 13 +#define TMC4361A_MULTI_TURN_IN_SIGNED_FIELD ((RegisterField) {TMC4361A_MULTI_TURN_IN_SIGNED_MASK, TMC4361A_MULTI_TURN_IN_SIGNED_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_MULTI_TURN_OUT_EN_MASK 0x00004000 +#define TMC4361A_MULTI_TURN_OUT_EN_SHIFT 14 +#define TMC4361A_MULTI_TURN_OUT_EN_FIELD ((RegisterField) {TMC4361A_MULTI_TURN_OUT_EN_MASK, TMC4361A_MULTI_TURN_OUT_EN_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_USE_USTEPS_INSTEAD_OF_XRANGE_MASK 0x00008000 +#define TMC4361A_USE_USTEPS_INSTEAD_OF_XRANGE_SHIFT 15 +#define TMC4361A_USE_USTEPS_INSTEAD_OF_XRANGE_FIELD ((RegisterField) {TMC4361A_USE_USTEPS_INSTEAD_OF_XRANGE_MASK, TMC4361A_USE_USTEPS_INSTEAD_OF_XRANGE_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_CALC_MULTI_TURN_BEHAV_MASK 0x00010000 +#define TMC4361A_CALC_MULTI_TURN_BEHAV_SHIFT 16 +#define TMC4361A_CALC_MULTI_TURN_BEHAV_FIELD ((RegisterField) {TMC4361A_CALC_MULTI_TURN_BEHAV_MASK, TMC4361A_CALC_MULTI_TURN_BEHAV_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_SSI_MULTI_CYCLE_DATA_MASK 0x00020000 +#define TMC4361A_SSI_MULTI_CYCLE_DATA_SHIFT 17 +#define TMC4361A_SSI_MULTI_CYCLE_DATA_FIELD ((RegisterField) {TMC4361A_SSI_MULTI_CYCLE_DATA_MASK, TMC4361A_SSI_MULTI_CYCLE_DATA_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_SSI_GRAY_CODE_EN_MASK 0x00040000 +#define TMC4361A_SSI_GRAY_CODE_EN_SHIFT 18 +#define TMC4361A_SSI_GRAY_CODE_EN_FIELD ((RegisterField) {TMC4361A_SSI_GRAY_CODE_EN_MASK, TMC4361A_SSI_GRAY_CODE_EN_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_LEFT_ALIGNED_DATA_MASK 0x00080000 +#define TMC4361A_LEFT_ALIGNED_DATA_SHIFT 19 +#define TMC4361A_LEFT_ALIGNED_DATA_FIELD ((RegisterField) {TMC4361A_LEFT_ALIGNED_DATA_MASK, TMC4361A_LEFT_ALIGNED_DATA_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_SPI_DATA_ON_CS_MASK 0x00100000 +#define TMC4361A_SPI_DATA_ON_CS_SHIFT 20 +#define TMC4361A_SPI_DATA_ON_CS_FIELD ((RegisterField) {TMC4361A_SPI_DATA_ON_CS_MASK, TMC4361A_SPI_DATA_ON_CS_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_SPI_LOW_BEFORE_CS_MASK 0x00200000 +#define TMC4361A_SPI_LOW_BEFORE_CS_SHIFT 21 +#define TMC4361A_SPI_LOW_BEFORE_CS_FIELD ((RegisterField) {TMC4361A_SPI_LOW_BEFORE_CS_MASK, TMC4361A_SPI_LOW_BEFORE_CS_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_REGULATION_MODUS_MASK 0x00c00000 +#define TMC4361A_REGULATION_MODUS_SHIFT 22 +#define TMC4361A_REGULATION_MODUS_FIELD ((RegisterField) {TMC4361A_REGULATION_MODUS_MASK, TMC4361A_REGULATION_MODUS_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_CL_CALIBRATION_EN_MASK 0x01000000 +#define TMC4361A_CL_CALIBRATION_EN_SHIFT 24 +#define TMC4361A_CL_CALIBRATION_EN_FIELD ((RegisterField) {TMC4361A_CL_CALIBRATION_EN_MASK, TMC4361A_CL_CALIBRATION_EN_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_CL_EMF_EN_MASK 0x02000000 +#define TMC4361A_CL_EMF_EN_SHIFT 25 +#define TMC4361A_CL_EMF_EN_FIELD ((RegisterField) {TMC4361A_CL_EMF_EN_MASK, TMC4361A_CL_EMF_EN_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_CL_CLR_XACT_MASK 0x04000000 +#define TMC4361A_CL_CLR_XACT_SHIFT 26 +#define TMC4361A_CL_CLR_XACT_FIELD ((RegisterField) {TMC4361A_CL_CLR_XACT_MASK, TMC4361A_CL_CLR_XACT_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_CL_VLIMIT_EN_MASK 0x08000000 +#define TMC4361A_CL_VLIMIT_EN_SHIFT 27 +#define TMC4361A_CL_VLIMIT_EN_FIELD ((RegisterField) {TMC4361A_CL_VLIMIT_EN_MASK, TMC4361A_CL_VLIMIT_EN_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_CL_VELOCITY_MODE_EN_MASK 0x10000000 +#define TMC4361A_CL_VELOCITY_MODE_EN_SHIFT 28 +#define TMC4361A_CL_VELOCITY_MODE_EN_FIELD ((RegisterField) {TMC4361A_CL_VELOCITY_MODE_EN_MASK, TMC4361A_CL_VELOCITY_MODE_EN_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_INVERT_ENC_DIR_MASK 0x20000000 +#define TMC4361A_INVERT_ENC_DIR_SHIFT 29 +#define TMC4361A_INVERT_ENC_DIR_FIELD ((RegisterField) {TMC4361A_INVERT_ENC_DIR_MASK, TMC4361A_INVERT_ENC_DIR_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_ENC_OUT_GRAY_MASK 0x40000000 +#define TMC4361A_ENC_OUT_GRAY_SHIFT 30 +#define TMC4361A_ENC_OUT_GRAY_FIELD ((RegisterField) {TMC4361A_ENC_OUT_GRAY_MASK, TMC4361A_ENC_OUT_GRAY_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_NO_ENC_VEL_PREPROC_MASK 0x80000000 +#define TMC4361A_NO_ENC_VEL_PREPROC_SHIFT 31 +#define TMC4361A_NO_ENC_VEL_PREPROC_FIELD ((RegisterField) {TMC4361A_NO_ENC_VEL_PREPROC_MASK, TMC4361A_NO_ENC_VEL_PREPROC_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_SERIAL_ENC_VARIATION_LIMIT_MASK 0x80000000 +#define TMC4361A_SERIAL_ENC_VARIATION_LIMIT_SHIFT 31 +#define TMC4361A_SERIAL_ENC_VARIATION_LIMIT_FIELD ((RegisterField) {TMC4361A_SERIAL_ENC_VARIATION_LIMIT_MASK, TMC4361A_SERIAL_ENC_VARIATION_LIMIT_SHIFT, TMC4361A_ENC_IN_CONF, false}) +#define TMC4361A_SINGLE_TURN_RES_MASK 0x0000001f +#define TMC4361A_SINGLE_TURN_RES_SHIFT 0 +#define TMC4361A_SINGLE_TURN_RES_FIELD ((RegisterField) {TMC4361A_SINGLE_TURN_RES_MASK, TMC4361A_SINGLE_TURN_RES_SHIFT, TMC4361A_ENC_IN_DATA, false}) +#define TMC4361A_MULTI_TURN_RES_MASK 0x000003e0 +#define TMC4361A_MULTI_TURN_RES_SHIFT 5 +#define TMC4361A_MULTI_TURN_RES_FIELD ((RegisterField) {TMC4361A_MULTI_TURN_RES_MASK, TMC4361A_MULTI_TURN_RES_SHIFT, TMC4361A_ENC_IN_DATA, false}) +#define TMC4361A_STATUS_BIT_CNT_MASK 0x00000c00 +#define TMC4361A_STATUS_BIT_CNT_SHIFT 10 +#define TMC4361A_STATUS_BIT_CNT_FIELD ((RegisterField) {TMC4361A_STATUS_BIT_CNT_MASK, TMC4361A_STATUS_BIT_CNT_SHIFT, TMC4361A_ENC_IN_DATA, false}) +#define TMC4361A_SERIAL_ADDR_BITS_MASK 0x00ff0000 +#define TMC4361A_SERIAL_ADDR_BITS_SHIFT 16 +#define TMC4361A_SERIAL_ADDR_BITS_FIELD ((RegisterField) {TMC4361A_SERIAL_ADDR_BITS_MASK, TMC4361A_SERIAL_ADDR_BITS_SHIFT, TMC4361A_ENC_IN_DATA, false}) +#define TMC4361A_SERIAL_DATA_BITS_MASK 0xff000000 +#define TMC4361A_SERIAL_DATA_BITS_SHIFT 24 +#define TMC4361A_SERIAL_DATA_BITS_FIELD ((RegisterField) {TMC4361A_SERIAL_DATA_BITS_MASK, TMC4361A_SERIAL_DATA_BITS_SHIFT, TMC4361A_ENC_IN_DATA, false}) +#define TMC4361A_SINGLE_TURN_RES_OUT_MASK 0x0000001f +#define TMC4361A_SINGLE_TURN_RES_OUT_SHIFT 0 +#define TMC4361A_SINGLE_TURN_RES_OUT_FIELD ((RegisterField) {TMC4361A_SINGLE_TURN_RES_OUT_MASK, TMC4361A_SINGLE_TURN_RES_OUT_SHIFT, TMC4361A_ENC_OUT_DATA, false}) +#define TMC4361A_MULTI_TURN_RES_OUT_MASK 0x000003e0 +#define TMC4361A_MULTI_TURN_RES_OUT_SHIFT 5 +#define TMC4361A_MULTI_TURN_RES_OUT_FIELD ((RegisterField) {TMC4361A_MULTI_TURN_RES_OUT_MASK, TMC4361A_MULTI_TURN_RES_OUT_SHIFT, TMC4361A_ENC_OUT_DATA, false}) +#define TMC4361A_MSTEP_PER_FS_MASK 0x0000000f +#define TMC4361A_MSTEP_PER_FS_SHIFT 0 +#define TMC4361A_MSTEP_PER_FS_FIELD ((RegisterField) {TMC4361A_MSTEP_PER_FS_MASK, TMC4361A_MSTEP_PER_FS_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_FS_PER_REV_MASK 0x0000fff0 +#define TMC4361A_FS_PER_REV_SHIFT 4 +#define TMC4361A_FS_PER_REV_FIELD ((RegisterField) {TMC4361A_FS_PER_REV_MASK, TMC4361A_FS_PER_REV_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_SG_MASK 0x00010000 +#define TMC4361A_SG_SHIFT 16 +#define TMC4361A_SG_FIELD ((RegisterField) {TMC4361A_SG_MASK, TMC4361A_SG_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_OT_MASK 0x00020000 +#define TMC4361A_OT_SHIFT 17 +#define TMC4361A_OT_FIELD ((RegisterField) {TMC4361A_OT_MASK, TMC4361A_OT_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_OTPW_MASK 0x00040000 +#define TMC4361A_OTPW_SHIFT 18 +#define TMC4361A_OTPW_FIELD ((RegisterField) {TMC4361A_OTPW_MASK, TMC4361A_OTPW_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_S2GA_MASK 0x00080000 +#define TMC4361A_S2GA_SHIFT 19 +#define TMC4361A_S2GA_FIELD ((RegisterField) {TMC4361A_S2GA_MASK, TMC4361A_S2GA_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_S2GB_MASK 0x00100000 +#define TMC4361A_S2GB_SHIFT 20 +#define TMC4361A_S2GB_FIELD ((RegisterField) {TMC4361A_S2GB_MASK, TMC4361A_S2GB_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_OLA_MASK 0x00200000 +#define TMC4361A_OLA_SHIFT 21 +#define TMC4361A_OLA_FIELD ((RegisterField) {TMC4361A_OLA_MASK, TMC4361A_OLA_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_OLB_MASK 0x00400000 +#define TMC4361A_OLB_SHIFT 22 +#define TMC4361A_OLB_FIELD ((RegisterField) {TMC4361A_OLB_MASK, TMC4361A_OLB_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_STST_MASK 0x00800000 +#define TMC4361A_STST_SHIFT 23 +#define TMC4361A_STST_FIELD ((RegisterField) {TMC4361A_STST_MASK, TMC4361A_STST_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_UV_SF_MASK 0x00010000 +#define TMC4361A_UV_SF_SHIFT 16 +#define TMC4361A_UV_SF_FIELD ((RegisterField) {TMC4361A_UV_SF_MASK, TMC4361A_UV_SF_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_OCA_MASK 0x00080000 +#define TMC4361A_OCA_SHIFT 19 +#define TMC4361A_OCA_FIELD ((RegisterField) {TMC4361A_OCA_MASK, TMC4361A_OCA_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_OCB_MASK 0x00100000 +#define TMC4361A_OCB_SHIFT 20 +#define TMC4361A_OCB_FIELD ((RegisterField) {TMC4361A_OCB_MASK, TMC4361A_OCB_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_OCHS_MASK 0x00800000 +#define TMC4361A_OCHS_SHIFT 23 +#define TMC4361A_OCHS_FIELD ((RegisterField) {TMC4361A_OCHS_MASK, TMC4361A_OCHS_SHIFT, TMC4361A_STEP_CONF, false}) +#define TMC4361A_TARGET_REACHED_MASK 0x00000001 +#define TMC4361A_TARGET_REACHED_SHIFT 0 +#define TMC4361A_TARGET_REACHED_FIELD ((RegisterField) {TMC4361A_TARGET_REACHED_MASK, TMC4361A_TARGET_REACHED_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_POS_COMP_REACHED_MASK 0x00000002 +#define TMC4361A_POS_COMP_REACHED_SHIFT 1 +#define TMC4361A_POS_COMP_REACHED_FIELD ((RegisterField) {TMC4361A_POS_COMP_REACHED_MASK, TMC4361A_POS_COMP_REACHED_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_VEL_REACHED_MASK 0x00000004 +#define TMC4361A_VEL_REACHED_SHIFT 2 +#define TMC4361A_VEL_REACHED_FIELD ((RegisterField) {TMC4361A_VEL_REACHED_MASK, TMC4361A_VEL_REACHED_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_VEL_STATE_00_MASK 0x00000008 +#define TMC4361A_VEL_STATE_00_SHIFT 3 +#define TMC4361A_VEL_STATE_00_FIELD ((RegisterField) {TMC4361A_VEL_STATE_00_MASK, TMC4361A_VEL_STATE_00_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_VEL_STATE_01_MASK 0x00000010 +#define TMC4361A_VEL_STATE_01_SHIFT 4 +#define TMC4361A_VEL_STATE_01_FIELD ((RegisterField) {TMC4361A_VEL_STATE_01_MASK, TMC4361A_VEL_STATE_01_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_VEL_STATE_10_MASK 0x00000020 +#define TMC4361A_VEL_STATE_10_SHIFT 5 +#define TMC4361A_VEL_STATE_10_FIELD ((RegisterField) {TMC4361A_VEL_STATE_10_MASK, TMC4361A_VEL_STATE_10_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_RAMP_STATE_00_MASK 0x00000040 +#define TMC4361A_RAMP_STATE_00_SHIFT 6 +#define TMC4361A_RAMP_STATE_00_FIELD ((RegisterField) {TMC4361A_RAMP_STATE_00_MASK, TMC4361A_RAMP_STATE_00_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_RAMP_STATE_01_MASK 0x00000080 +#define TMC4361A_RAMP_STATE_01_SHIFT 7 +#define TMC4361A_RAMP_STATE_01_FIELD ((RegisterField) {TMC4361A_RAMP_STATE_01_MASK, TMC4361A_RAMP_STATE_01_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_RAMP_STATE_10_MASK 0x00000100 +#define TMC4361A_RAMP_STATE_10_SHIFT 8 +#define TMC4361A_RAMP_STATE_10_FIELD ((RegisterField) {TMC4361A_RAMP_STATE_10_MASK, TMC4361A_RAMP_STATE_10_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_MAX_PHASE_TRAP_MASK 0x00000200 +#define TMC4361A_MAX_PHASE_TRAP_SHIFT 9 +#define TMC4361A_MAX_PHASE_TRAP_FIELD ((RegisterField) {TMC4361A_MAX_PHASE_TRAP_MASK, TMC4361A_MAX_PHASE_TRAP_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_FROZEN_MASK 0x00000400 +#define TMC4361A_FROZEN_SHIFT 10 +#define TMC4361A_FROZEN_FIELD ((RegisterField) {TMC4361A_FROZEN_MASK, TMC4361A_FROZEN_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_STOPL_EVENT_MASK 0x00000800 +#define TMC4361A_STOPL_EVENT_SHIFT 11 +#define TMC4361A_STOPL_EVENT_FIELD ((RegisterField) {TMC4361A_STOPL_EVENT_MASK, TMC4361A_STOPL_EVENT_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_STOPR_EVENT_MASK 0x00001000 +#define TMC4361A_STOPR_EVENT_SHIFT 12 +#define TMC4361A_STOPR_EVENT_FIELD ((RegisterField) {TMC4361A_STOPR_EVENT_MASK, TMC4361A_STOPR_EVENT_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_VSTOPL_ACTIVE_MASK 0x00002000 +#define TMC4361A_VSTOPL_ACTIVE_SHIFT 13 +#define TMC4361A_VSTOPL_ACTIVE_FIELD ((RegisterField) {TMC4361A_VSTOPL_ACTIVE_MASK, TMC4361A_VSTOPL_ACTIVE_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_VSTOPR_ACTIVE_MASK 0x00004000 +#define TMC4361A_VSTOPR_ACTIVE_SHIFT 14 +#define TMC4361A_VSTOPR_ACTIVE_FIELD ((RegisterField) {TMC4361A_VSTOPR_ACTIVE_MASK, TMC4361A_VSTOPR_ACTIVE_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_HOME_ERROR_MASK 0x00008000 +#define TMC4361A_HOME_ERROR_SHIFT 15 +#define TMC4361A_HOME_ERROR_FIELD ((RegisterField) {TMC4361A_HOME_ERROR_MASK, TMC4361A_HOME_ERROR_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_XLATCH_DONE_MASK 0x00010000 +#define TMC4361A_XLATCH_DONE_SHIFT 16 +#define TMC4361A_XLATCH_DONE_FIELD ((RegisterField) {TMC4361A_XLATCH_DONE_MASK, TMC4361A_XLATCH_DONE_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_FS_ACTIVE_MASK 0x00020000 +#define TMC4361A_FS_ACTIVE_SHIFT 17 +#define TMC4361A_FS_ACTIVE_FIELD ((RegisterField) {TMC4361A_FS_ACTIVE_MASK, TMC4361A_FS_ACTIVE_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_ENC_FAIL_MASK 0x00040000 +#define TMC4361A_ENC_FAIL_SHIFT 18 +#define TMC4361A_ENC_FAIL_FIELD ((RegisterField) {TMC4361A_ENC_FAIL_MASK, TMC4361A_ENC_FAIL_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_N_ACTIVE_MASK 0x00080000 +#define TMC4361A_N_ACTIVE_SHIFT 19 +#define TMC4361A_N_ACTIVE_FIELD ((RegisterField) {TMC4361A_N_ACTIVE_MASK, TMC4361A_N_ACTIVE_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_ENC_DONE_MASK 0x00100000 +#define TMC4361A_ENC_DONE_SHIFT 20 +#define TMC4361A_ENC_DONE_FIELD ((RegisterField) {TMC4361A_ENC_DONE_MASK, TMC4361A_ENC_DONE_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_SER_ENC_DATA_FAIL_MASK 0x00200000 +#define TMC4361A_SER_ENC_DATA_FAIL_SHIFT 21 +#define TMC4361A_SER_ENC_DATA_FAIL_FIELD ((RegisterField) {TMC4361A_SER_ENC_DATA_FAIL_MASK, TMC4361A_SER_ENC_DATA_FAIL_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_SER_DATA_DONE_MASK 0x00800000 +#define TMC4361A_SER_DATA_DONE_SHIFT 23 +#define TMC4361A_SER_DATA_DONE_FIELD ((RegisterField) {TMC4361A_SER_DATA_DONE_MASK, TMC4361A_SER_DATA_DONE_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_SERIAL_ENC_FLAGS_MASK 0x01000000 +#define TMC4361A_SERIAL_ENC_FLAGS_SHIFT 24 +#define TMC4361A_SERIAL_ENC_FLAGS_FIELD ((RegisterField) {TMC4361A_SERIAL_ENC_FLAGS_MASK, TMC4361A_SERIAL_ENC_FLAGS_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_COVER_DONE_MASK 0x02000000 +#define TMC4361A_COVER_DONE_SHIFT 25 +#define TMC4361A_COVER_DONE_FIELD ((RegisterField) {TMC4361A_COVER_DONE_MASK, TMC4361A_COVER_DONE_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_ENC_VEL0_MASK 0x04000000 +#define TMC4361A_ENC_VEL0_SHIFT 26 +#define TMC4361A_ENC_VEL0_FIELD ((RegisterField) {TMC4361A_ENC_VEL0_MASK, TMC4361A_ENC_VEL0_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_CL_MAX_MASK 0x08000000 +#define TMC4361A_CL_MAX_SHIFT 27 +#define TMC4361A_CL_MAX_FIELD ((RegisterField) {TMC4361A_CL_MAX_MASK, TMC4361A_CL_MAX_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_CL_FIT_MASK 0x10000000 +#define TMC4361A_CL_FIT_SHIFT 28 +#define TMC4361A_CL_FIT_FIELD ((RegisterField) {TMC4361A_CL_FIT_MASK, TMC4361A_CL_FIT_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_MOTOR_EV_MASK 0x40000000 +#define TMC4361A_MOTOR_EV_SHIFT 30 +#define TMC4361A_MOTOR_EV_FIELD ((RegisterField) {TMC4361A_MOTOR_EV_MASK, TMC4361A_MOTOR_EV_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_RST_EV_MASK 0x80000000 +#define TMC4361A_RST_EV_SHIFT 31 +#define TMC4361A_RST_EV_FIELD ((RegisterField) {TMC4361A_RST_EV_MASK, TMC4361A_RST_EV_SHIFT, TMC4361A_SPI_STATUS_SELECTION, false}) +#define TMC4361A_TARGET_REACHED_F_MASK 0x00000001 +#define TMC4361A_TARGET_REACHED_F_SHIFT 0 +#define TMC4361A_TARGET_REACHED_F_FIELD ((RegisterField) {TMC4361A_TARGET_REACHED_F_MASK, TMC4361A_TARGET_REACHED_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_POS_COMP_REACHED_F_MASK 0x00000002 +#define TMC4361A_POS_COMP_REACHED_F_SHIFT 1 +#define TMC4361A_POS_COMP_REACHED_F_FIELD ((RegisterField) {TMC4361A_POS_COMP_REACHED_F_MASK, TMC4361A_POS_COMP_REACHED_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_VEL_REACHED_F_MASK 0x00000004 +#define TMC4361A_VEL_REACHED_F_SHIFT 2 +#define TMC4361A_VEL_REACHED_F_FIELD ((RegisterField) {TMC4361A_VEL_REACHED_F_MASK, TMC4361A_VEL_REACHED_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_VEL_STATE_F_MASK 0x00000018 +#define TMC4361A_VEL_STATE_F_SHIFT 3 +#define TMC4361A_VEL_STATE_F_FIELD ((RegisterField) {TMC4361A_VEL_STATE_F_MASK, TMC4361A_VEL_STATE_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_RAMP_STATE_F_MASK 0x00000060 +#define TMC4361A_RAMP_STATE_F_SHIFT 5 +#define TMC4361A_RAMP_STATE_F_FIELD ((RegisterField) {TMC4361A_RAMP_STATE_F_MASK, TMC4361A_RAMP_STATE_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_STOPL_ACTIVE_F_MASK 0x00000080 +#define TMC4361A_STOPL_ACTIVE_F_SHIFT 7 +#define TMC4361A_STOPL_ACTIVE_F_FIELD ((RegisterField) {TMC4361A_STOPL_ACTIVE_F_MASK, TMC4361A_STOPL_ACTIVE_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_STOPR_ACTIVE_F_MASK 0x00000100 +#define TMC4361A_STOPR_ACTIVE_F_SHIFT 8 +#define TMC4361A_STOPR_ACTIVE_F_FIELD ((RegisterField) {TMC4361A_STOPR_ACTIVE_F_MASK, TMC4361A_STOPR_ACTIVE_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_VSTOPL_ACTIVE_F_MASK 0x00000200 +#define TMC4361A_VSTOPL_ACTIVE_F_SHIFT 9 +#define TMC4361A_VSTOPL_ACTIVE_F_FIELD ((RegisterField) {TMC4361A_VSTOPL_ACTIVE_F_MASK, TMC4361A_VSTOPL_ACTIVE_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_VSTOPR_ACTIVE_F_MASK 0x00000400 +#define TMC4361A_VSTOPR_ACTIVE_F_SHIFT 10 +#define TMC4361A_VSTOPR_ACTIVE_F_FIELD ((RegisterField) {TMC4361A_VSTOPR_ACTIVE_F_MASK, TMC4361A_VSTOPR_ACTIVE_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_ACTIVE_STALL_F_MASK 0x00000800 +#define TMC4361A_ACTIVE_STALL_F_SHIFT 11 +#define TMC4361A_ACTIVE_STALL_F_FIELD ((RegisterField) {TMC4361A_ACTIVE_STALL_F_MASK, TMC4361A_ACTIVE_STALL_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_HOME_ERROR_F_MASK 0x00001000 +#define TMC4361A_HOME_ERROR_F_SHIFT 12 +#define TMC4361A_HOME_ERROR_F_FIELD ((RegisterField) {TMC4361A_HOME_ERROR_F_MASK, TMC4361A_HOME_ERROR_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_FS_ACTIVE_F_MASK 0x00002000 +#define TMC4361A_FS_ACTIVE_F_SHIFT 13 +#define TMC4361A_FS_ACTIVE_F_FIELD ((RegisterField) {TMC4361A_FS_ACTIVE_F_MASK, TMC4361A_FS_ACTIVE_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_ENC_FAIL_F_MASK 0x00004000 +#define TMC4361A_ENC_FAIL_F_SHIFT 14 +#define TMC4361A_ENC_FAIL_F_FIELD ((RegisterField) {TMC4361A_ENC_FAIL_F_MASK, TMC4361A_ENC_FAIL_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_N_ACTIVE_F_MASK 0x00008000 +#define TMC4361A_N_ACTIVE_F_SHIFT 15 +#define TMC4361A_N_ACTIVE_F_FIELD ((RegisterField) {TMC4361A_N_ACTIVE_F_MASK, TMC4361A_N_ACTIVE_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_ENC_LATCH_F_MASK 0x00010000 +#define TMC4361A_ENC_LATCH_F_SHIFT 16 +#define TMC4361A_ENC_LATCH_F_FIELD ((RegisterField) {TMC4361A_ENC_LATCH_F_MASK, TMC4361A_ENC_LATCH_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_MULTI_CYCLE_FAIL_F___SER_ENC_VAR_F_MASK 0x00020000 +#define TMC4361A_MULTI_CYCLE_FAIL_F___SER_ENC_VAR_F_SHIFT 17 +#define TMC4361A_MULTI_CYCLE_FAIL_F___SER_ENC_VAR_F_FIELD ((RegisterField) {TMC4361A_MULTI_CYCLE_FAIL_F___SER_ENC_VAR_F_MASK, TMC4361A_MULTI_CYCLE_FAIL_F___SER_ENC_VAR_F_SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_SERIAL_ENC_FLAG___MASK 0x00100000 +#define TMC4361A_SERIAL_ENC_FLAG___SHIFT 20 +#define TMC4361A_SERIAL_ENC_FLAG___FIELD ((RegisterField) {TMC4361A_SERIAL_ENC_FLAG___MASK, TMC4361A_SERIAL_ENC_FLAG___SHIFT, TMC4361A_STATUS, false}) +#define TMC4361A_STP_LENGTH_ADD_MASK 0x0000FFFF +#define TMC4361A_STP_LENGTH_ADD_SHIFT 0 +#define TMC4361A_STP_LENGTH_ADD_FIELD ((RegisterField) {TMC4361A_STP_LENGTH_ADD_MASK, TMC4361A_STP_LENGTH_ADD_SHIFT, TMC4361A_STP_LENGTH_ADD, false}) +#define TMC4361A_DIR_SETUP_TIME_MASK 0xFFFF0000 +#define TMC4361A_DIR_SETUP_TIME_SHIFT 16 +#define TMC4361A_DIR_SETUP_TIME_FIELD ((RegisterField) {TMC4361A_DIR_SETUP_TIME_MASK, TMC4361A_DIR_SETUP_TIME_SHIFT, TMC4361A_DIR_SETUP_TIME, false}) +#define TMC4361A_START_OUT_ADD_MASK 0xFFFFFFFF +#define TMC4361A_START_OUT_ADD_SHIFT 0 +#define TMC4361A_START_OUT_ADD_FIELD ((RegisterField) {TMC4361A_START_OUT_ADD_MASK, TMC4361A_START_OUT_ADD_SHIFT, TMC4361A_START_OUT_ADD, false}) +#define TMC4361A_GEAR_RATIO_MASK 0xFFFFFFFF +#define TMC4361A_GEAR_RATIO_SHIFT 0 +#define TMC4361A_GEAR_RATIO_FIELD ((RegisterField) {TMC4361A_GEAR_RATIO_MASK, TMC4361A_GEAR_RATIO_SHIFT, TMC4361A_GEAR_RATIO, true}) +#define TMC4361A_START_DELAY_MASK 0xFFFFFFFF +#define TMC4361A_START_DELAY_SHIFT 0 +#define TMC4361A_START_DELAY_FIELD ((RegisterField) {TMC4361A_START_DELAY_MASK, TMC4361A_START_DELAY_SHIFT, TMC4361A_START_DELAY, false}) +#define TMC4361A_CLK_GATING_DELAY_MASK 0xFFFFFFFF +#define TMC4361A_CLK_GATING_DELAY_SHIFT 0 +#define TMC4361A_CLK_GATING_DELAY_FIELD ((RegisterField) {TMC4361A_CLK_GATING_DELAY_MASK, TMC4361A_CLK_GATING_DELAY_SHIFT, TMC4361A_CLK_GATING_DELAY, false}) +#define TMC4361A_STDBY_DELAY_MASK 0xFFFFFFFF +#define TMC4361A_STDBY_DELAY_SHIFT 0 +#define TMC4361A_STDBY_DELAY_FIELD ((RegisterField) {TMC4361A_STDBY_DELAY_MASK, TMC4361A_STDBY_DELAY_SHIFT, TMC4361A_STDBY_DELAY, false}) +#define TMC4361A_FREEWHEEL_DELAY_MASK 0xFFFFFFFF +#define TMC4361A_FREEWHEEL_DELAY_SHIFT 0 +#define TMC4361A_FREEWHEEL_DELAY_FIELD ((RegisterField) {TMC4361A_FREEWHEEL_DELAY_MASK, TMC4361A_FREEWHEEL_DELAY_SHIFT, TMC4361A_FREEWHEEL_DELAY, false}) +#define TMC4361A_VDRV_SCALE_LIMIT_MASK 0x00FFFFFF +#define TMC4361A_VDRV_SCALE_LIMIT_SHIFT 0 +#define TMC4361A_VDRV_SCALE_LIMIT_FIELD ((RegisterField) {TMC4361A_VDRV_SCALE_LIMIT_MASK, TMC4361A_VDRV_SCALE_LIMIT_SHIFT, TMC4361A_VDRV_SCALE_LIMIT, false}) +#define TMC4361A_PWM_VMAX_MASK 0x00FFFFFF +#define TMC4361A_PWM_VMAX_SHIFT 0 +#define TMC4361A_PWM_VMAX_FIELD ((RegisterField) {TMC4361A_PWM_VMAX_MASK, TMC4361A_PWM_VMAX_SHIFT, TMC4361A_PWM_VMAX, false}) +#define TMC4361A_UP_SCALE_DELAY_MASK 0x00FFFFFF +#define TMC4361A_UP_SCALE_DELAY_SHIFT 0 +#define TMC4361A_UP_SCALE_DELAY_FIELD ((RegisterField) {TMC4361A_UP_SCALE_DELAY_MASK, TMC4361A_UP_SCALE_DELAY_SHIFT, TMC4361A_UP_SCALE_DELAY, false}) +#define TMC4361A_CL_UPSCALE_DELAY_MASK 0x00FFFFFF +#define TMC4361A_CL_UPSCALE_DELAY_SHIFT 0 +#define TMC4361A_CL_UPSCALE_DELAY_FIELD ((RegisterField) {TMC4361A_CL_UPSCALE_DELAY_MASK, TMC4361A_CL_UPSCALE_DELAY_SHIFT, TMC4361A_CL_UPSCALE_DELAY, false}) +//#define TMC4361A_UP_SCALE_dY_FIELD ((RegisterField) {TMC4361A_UP_SCALE_DELAY_MASK, TMC4361A_UP_SCALE_DELAY_SHIFT, TMC4361A_UP_SCALE_DELAY, false}) +#define TMC4361A_HOLD_SCALE_DELAY_MASK 0x00FFFFFF +#define TMC4361A_HOLD_SCALE_DELAY_SHIFT 0 +#define TMC4361A_HOLD_SCALE_DELAY_FIELD ((RegisterField) {TMC4361A_HOLD_SCALE_DELAY_MASK, TMC4361A_HOLD_SCALE_DELAY_SHIFT, TMC4361A_HOLD_SCALE_DELAY, false}) +#define TMC4361A_CL_DNSCALE_DELAY_MASK 0x00FFFFFF +#define TMC4361A_CL_DNSCALE_DELAY_SHIFT 0 +#define TMC4361A_CL_DNSCALE_DELAY_FIELD ((RegisterField) {TMC4361A_CL_DNSCALE_DELAY_MASK, TMC4361A_CL_DNSCALE_DELAY_SHIFT, TMC4361A_CL_DNSCALE_DELAY, false}) +#define TMC4361A_DRV_SCALE_DELAY_MASK 0x00FFFFFF +#define TMC4361A_DRV_SCALE_DELAY_SHIFT 0 +#define TMC4361A_DRV_SCALE_DELAY_FIELD ((RegisterField) {TMC4361A_DRV_SCALE_DELAY_MASK, TMC4361A_DRV_SCALE_DELAY_SHIFT, TMC4361A_DRV_SCALE_DELAY, false}) +#define TMC4361A_BOOST_TIME_MASK 0x00FFFFFF +#define TMC4361A_BOOST_TIME_SHIFT 0 +#define TMC4361A_BOOST_TIME_FIELD ((RegisterField) {TMC4361A_BOOST_TIME_MASK, TMC4361A_BOOST_TIME_SHIFT, TMC4361A_BOOST_TIME, false}) +#define TMC4361A_CL_BETA_MASK 0x000001FF +#define TMC4361A_CL_BETA_SHIFT 0 +#define TMC4361A_CL_BETA_FIELD ((RegisterField) {TMC4361A_CL_BETA_MASK, TMC4361A_CL_BETA_SHIFT, TMC4361A_CL ANGLES, false}) +#define TMC4361A_CL_GAMMA_MASK 0x00FF0000 +#define TMC4361A_CL_GAMMA_SHIFT 16 +#define TMC4361A_CL_GAMMA_FIELD ((RegisterField) {TMC4361A_CL_GAMMA_MASK, TMC4361A_CL_GAMMA_SHIFT, TMC4361A_CL ANGLES, false}) +#define TMC4361A_SPI_SWITCH_VEL_MASK 0x00FFFFFF +#define TMC4361A_SPI_SWITCH_VEL_SHIFT 0 +#define TMC4361A_SPI_SWITCH_VEL_FIELD ((RegisterField) {TMC4361A_SPI_SWITCH_VEL_MASK, TMC4361A_SPI_SWITCH_VEL_SHIFT, TMC4361A_SPI_SWITCH_VEL, false}) +#define TMC4361A_DAC_ADDR_A_MASK 0x0000FFFF +#define TMC4361A_DAC_ADDR_A_SHIFT 0 +#define TMC4361A_DAC_ADDR_A_FIELD ((RegisterField) {TMC4361A_DAC_ADDR_A_MASK, TMC4361A_DAC_ADDR_A_SHIFT, TMC4361A_DAC_ADDR_A, false}) +#define TMC4361A_DAC_ADDR_B_MASK 0xFFFF0000 +#define TMC4361A_DAC_ADDR_B_SHIFT 16 +#define TMC4361A_DAC_ADDR_B_FIELD ((RegisterField) {TMC4361A_DAC_ADDR_B_MASK, TMC4361A_DAC_ADDR_B_SHIFT, TMC4361A_DAC_ADDR_B, false}) +#define TMC4361A_HOME_SAFETY_MARGIN_MASK 0x0000FFFF +#define TMC4361A_HOME_SAFETY_MARGIN_SHIFT 0 +#define TMC4361A_HOME_SAFETY_MARGIN_FIELD ((RegisterField) {TMC4361A_HOME_SAFETY_MARGIN_MASK, TMC4361A_HOME_SAFETY_MARGIN_SHIFT, TMC4361A_HOME_SAFETY_MARGIN, false}) +#define TMC4361A_PWM_FREQ_MASK 0x0000FFFF +#define TMC4361A_PWM_FREQ_SHIFT 0 +#define TMC4361A_PWM_FREQ_FIELD ((RegisterField) {TMC4361A_PWM_FREQ_MASK, TMC4361A_PWM_FREQ_SHIFT, TMC4361A_PWM_FREQ, false}) +#define TMC4361A_CHOPSYNC_DIV_MASK 0x00000FFF +#define TMC4361A_CHOPSYNC_DIV_SHIFT 0 +#define TMC4361A_CHOPSYNC_DIV_FIELD ((RegisterField) {TMC4361A_CHOPSYNC_DIV_MASK, TMC4361A_CHOPSYNC_DIV_SHIFT, TMC4361A_CHOPSYNC_DIV, false}) +#define TMC4361A_OPERATION_MODE_MASK 0x00000004 +#define TMC4361A_OPERATION_MODE_SHIFT 2 +#define TMC4361A_OPERATION_MODE_FIELD ((RegisterField) {TMC4361A_OPERATION_MODE_MASK, TMC4361A_OPERATION_MODE_SHIFT, TMC4361A_RAMPMODE, false}) +#define TMC4361A_RAMP_PROFILE_MASK 0x00000003 +#define TMC4361A_RAMP_PROFILE_SHIFT 0 +#define TMC4361A_RAMP_PROFILE_FIELD ((RegisterField) {TMC4361A_RAMP_PROFILE_MASK, TMC4361A_RAMP_PROFILE_SHIFT, TMC4361A_RAMPMODE, false}) +#define TMC4361A_XACTUAL_MASK 0xFFFFFFFF +#define TMC4361A_XACTUAL_SHIFT 0 +#define TMC4361A_XACTUAL_FIELD ((RegisterField) {TMC4361A_XACTUAL_MASK, TMC4361A_XACTUAL_SHIFT, TMC4361A_XACTUAL, true}) +#define TMC4361A_VACTUAL_MASK 0xFFFFFFFF +#define TMC4361A_VACTUAL_SHIFT 0 +#define TMC4361A_VACTUAL_FIELD ((RegisterField) {TMC4361A_VACTUAL_MASK, TMC4361A_VACTUAL_SHIFT, TMC4361A_VACTUAL, true}) +#define TMC4361A_AACTUAL_MASK 0xFFFFFFFF +#define TMC4361A_AACTUAL_SHIFT 0 +#define TMC4361A_AACTUAL_FIELD ((RegisterField) {TMC4361A_AACTUAL_MASK, TMC4361A_AACTUAL_SHIFT, TMC4361A_AACTUAL, true}) +#define TMC4361A_VMAX_MASK 0xFFFFFFFF +#define TMC4361A_VMAX_SHIFT 0 +#define TMC4361A_VMAX_FIELD ((RegisterField) {TMC4361A_VMAX_MASK, TMC4361A_VMAX_SHIFT, TMC4361A_VMAX, true}) +#define TMC4361A_VSTART_MASK 0x7FFFFFFF +#define TMC4361A_VSTART_SHIFT 0 +#define TMC4361A_VSTART_FIELD ((RegisterField) {TMC4361A_VSTART_MASK, TMC4361A_VSTART_SHIFT, TMC4361A_VSTART, false}) +#define TMC4361A_VSTOP_MASK 0x7FFFFFFF +#define TMC4361A_VSTOP_SHIFT 0 +#define TMC4361A_VSTOP_FIELD ((RegisterField) {TMC4361A_VSTOP_MASK, TMC4361A_VSTOP_SHIFT, TMC4361A_VSTOP, false}) +#define TMC4361A_VBREAK_MASK 0x7FFFFFFF +#define TMC4361A_VBREAK_SHIFT 0 +#define TMC4361A_VBREAK_FIELD ((RegisterField) {TMC4361A_VBREAK_MASK, TMC4361A_VBREAK_SHIFT, TMC4361A_VBREAK, false}) +#define TMC4361A_FREQUENCY_MODE_MASK 0x00FFFFFF +#define TMC4361A_FREQUENCY_MODE_SHIFT 0 +#define TMC4361A_FREQUENCY_MODE_FIELD ((RegisterField) {TMC4361A_FREQUENCY_MODE_MASK, TMC4361A_FREQUENCY_MODE_SHIFT, TMC4361A_AMAX, false}) +#define TMC4361A_DIRECT_MODE_MASK 0x00FFFFFF +#define TMC4361A_DIRECT_MODE_SHIFT 0 +#define TMC4361A_DIRECT_MODE_FIELD ((RegisterField) {TMC4361A_DIRECT_MODE_MASK, TMC4361A_DIRECT_MODE_SHIFT, TMC4361A_AMAX, false}) +#define TMC4361A_SIGN_AACT_MASK 0x80000000 +#define TMC4361A_SIGN_AACT_SHIFT 31 +#define TMC4361A_SIGN_AACT_FIELD ((RegisterField) {TMC4361A_SIGN_AACT_MASK, TMC4361A_SIGN_AACT_SHIFT, TMC4361A_ASTART, false}) +#define TMC4361A_CLK_FREQ_MASK 0x01FFFFFF +#define TMC4361A_CLK_FREQ_SHIFT 0 +#define TMC4361A_CLK_FREQ_FIELD ((RegisterField) {TMC4361A_CLK_FREQ_MASK, TMC4361A_CLK_FREQ_SHIFT, TMC4361A_CLK_FREQ, false}) +#define TMC4361A_POS_COMP_MASK 0xFFFFFFFF +#define TMC4361A_POS_COMP_SHIFT 0 +#define TMC4361A_POS_COMP_FIELD ((RegisterField) {TMC4361A_POS_COMP_MASK, TMC4361A_POS_COMP_SHIFT, TMC4361A_POS_COMP, true}) +#define TMC4361A_VIRT_STOP_LEFT_MASK 0xFFFFFFFF +#define TMC4361A_VIRT_STOP_LEFT_SHIFT 0 +#define TMC4361A_VIRT_STOP_LEFT_FIELD ((RegisterField) {TMC4361A_VIRT_STOP_LEFT_MASK, TMC4361A_VIRT_STOP_LEFT_SHIFT, TMC4361A_VIRT_STOP_LEFT, true}) +#define TMC4361A_VIRT_STOP_RIGHT_MASK 0xFFFFFFFF +#define TMC4361A_VIRT_STOP_RIGHT_SHIFT 0 +#define TMC4361A_VIRT_STOP_RIGHT_FIELD ((RegisterField) {TMC4361A_VIRT_STOP_RIGHT_MASK, TMC4361A_VIRT_STOP_RIGHT_SHIFT, TMC4361A_VIRT_STOP_RIGHT, true}) +#define TMC4361A_X_HOME_MASK 0xFFFFFFFF +#define TMC4361A_X_HOME_SHIFT 0 +#define TMC4361A_X_HOME_FIELD ((RegisterField) {TMC4361A_X_HOME_MASK, TMC4361A_X_HOME_SHIFT, TMC4361A_X_HOME, true}) +#define TMC4361A_X_LATCH_MASK 0xFFFFFFFF +#define TMC4361A_X_LATCH_SHIFT 0 +#define TMC4361A_X_LATCH_FIELD ((RegisterField) {TMC4361A_X_LATCH_MASK, TMC4361A_X_LATCH_SHIFT, TMC4361A_X_LATCH, true}) +#define TMC4361A_REV_CNT_MASK 0xFFFFFFFF +#define TMC4361A_REV_CNT_SHIFT 0 +#define TMC4361A_REV_CNT_FIELD ((RegisterField) {TMC4361A_REV_CNT_MASK, TMC4361A_REV_CNT_SHIFT, TMC4361A_REV_CNT, true}) +#define TMC4361A_X_RANGE_MASK 0xFFFFFFFF +#define TMC4361A_X_RANGE_SHIFT 0 +#define TMC4361A_X_RANGE_FIELD ((RegisterField) {TMC4361A_X_RANGE_MASK, TMC4361A_X_RANGE_SHIFT, TMC4361A_X_RANGE, false}) +#define TMC4361A_XTARGET_MASK 0xFFFFFFFF +#define TMC4361A_XTARGET_SHIFT 0 +#define TMC4361A_XTARGET_FIELD ((RegisterField) {TMC4361A_XTARGET_MASK, TMC4361A_XTARGET_SHIFT, TMC4361A_XTARGET, true}) +#define TMC4361A_X_PIPE0__XTARGET__MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE0__XTARGET__SHIFT 0 +#define TMC4361A_X_PIPE0__XTARGET__FIELD ((RegisterField) {TMC4361A_X_PIPE0__XTARGET__MASK, TMC4361A_X_PIPE0__XTARGET__SHIFT, TMC4361A_X_PIPE0, true}) +#define TMC4361A_X_PIPE0__POS_COMP__MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE0__POS_COMP__SHIFT 0 +#define TMC4361A_X_PIPE0__POS_COMP__FIELD ((RegisterField) {TMC4361A_X_PIPE0__POS_COMP__MASK, TMC4361A_X_PIPE0__POS_COMP__SHIFT, TMC4361A_X_PIPE0, true}) +#define TMC4361A_X_PIPE0__GEAR_RATIO__MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE0__GEAR_RATIO__SHIFT 0 +#define TMC4361A_X_PIPE0__GEAR_RATIO__FIELD ((RegisterField) {TMC4361A_X_PIPE0__GEAR_RATIO__MASK, TMC4361A_X_PIPE0__GEAR_RATIO__SHIFT, TMC4361A_X_PIPE0, true}) +#define TMC4361A_X_PIPE0__GENERAL_CONF__MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE0__GENERAL_CONF__SHIFT 0 +#define TMC4361A_X_PIPE0__GENERAL_CONF__FIELD ((RegisterField) {TMC4361A_X_PIPE0__GENERAL_CONF__MASK, TMC4361A_X_PIPE0__GENERAL_CONF__SHIFT, TMC4361A_X_PIPE0, false}) +#define TMC4361A_X_PIPE1__XTARGET__MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE1__XTARGET__SHIFT 0 +#define TMC4361A_X_PIPE1__XTARGET__FIELD ((RegisterField) {TMC4361A_X_PIPE1__XTARGET__MASK, TMC4361A_X_PIPE1__XTARGET__SHIFT, TMC4361A_X_PIPE1, true}) +#define TMC4361A_X_PIPE1__POS_COMP__MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE1__POS_COMP__SHIFT 0 +#define TMC4361A_X_PIPE1__POS_COMP__FIELD ((RegisterField) {TMC4361A_X_PIPE1__POS_COMP__MASK, TMC4361A_X_PIPE1__POS_COMP__SHIFT, TMC4361A_X_PIPE1, true}) +#define TMC4361A_X_PIPE1__GEAR_RATIO__MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE1__GEAR_RATIO__SHIFT 0 +#define TMC4361A_X_PIPE1__GEAR_RATIO__FIELD ((RegisterField) {TMC4361A_X_PIPE1__GEAR_RATIO__MASK, TMC4361A_X_PIPE1__GEAR_RATIO__SHIFT, TMC4361A_X_PIPE1, true}) +#define TMC4361A_X_PIPE1__GENERAL_CONF__MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE1__GENERAL_CONF__SHIFT 0 +#define TMC4361A_X_PIPE1__GENERAL_CONF__FIELD ((RegisterField) {TMC4361A_X_PIPE1__GENERAL_CONF__MASK, TMC4361A_X_PIPE1__GENERAL_CONF__SHIFT, TMC4361A_X_PIPE1, false}) +#define TMC4361A_X_PIPE2_MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE2_SHIFT 0 +#define TMC4361A_X_PIPE2_FIELD ((RegisterField) {TMC4361A_X_PIPE2_MASK, TMC4361A_X_PIPE2_SHIFT, TMC4361A_X_PIPE2, true}) +#define TMC4361A_X_PIPE3_MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE3_SHIFT 0 +#define TMC4361A_X_PIPE3_FIELD ((RegisterField) {TMC4361A_X_PIPE3_MASK, TMC4361A_X_PIPE3_SHIFT, TMC4361A_X_PIPE3, true}) +#define TMC4361A_X_PIPE4_MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE4_SHIFT 0 +#define TMC4361A_X_PIPE4_FIELD ((RegisterField) {TMC4361A_X_PIPE4_MASK, TMC4361A_X_PIPE4_SHIFT, TMC4361A_X_PIPE4, true}) +#define TMC4361A_X_PIPE5_MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE5_SHIFT 0 +#define TMC4361A_X_PIPE5_FIELD ((RegisterField) {TMC4361A_X_PIPE5_MASK, TMC4361A_X_PIPE5_SHIFT, TMC4361A_X_PIPE5, true}) +#define TMC4361A_X_PIPE6_MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE6_SHIFT 0 +#define TMC4361A_X_PIPE6_FIELD ((RegisterField) {TMC4361A_X_PIPE6_MASK, TMC4361A_X_PIPE6_SHIFT, TMC4361A_X_PIPE6, true}) +#define TMC4361A_X_PIPE7_MASK 0xFFFFFFFF +#define TMC4361A_X_PIPE7_SHIFT 0 +#define TMC4361A_X_PIPE7_FIELD ((RegisterField) {TMC4361A_X_PIPE7_MASK, TMC4361A_X_PIPE7_SHIFT, TMC4361A_X_PIPE7, true}) +#define TMC4361A_SH_REG0_VMAX_MASK 0xFFFFFFFF +#define TMC4361A_SH_REG0_VMAX_SHIFT 0 +#define TMC4361A_SH_REG0_VMAX_FIELD ((RegisterField) {TMC4361A_SH_REG0_VMAX_MASK, TMC4361A_SH_REG0_VMAX_SHIFT, TMC4361A_SH_REG0, true}) +#define TMC4361A_SH_REG1_AMAX_MASK 0x00FFFFFF +#define TMC4361A_SH_REG1_AMAX_SHIFT 0 +#define TMC4361A_SH_REG1_AMAX_FIELD ((RegisterField) {TMC4361A_SH_REG1_AMAX_MASK, TMC4361A_SH_REG1_AMAX_SHIFT, TMC4361A_SH_REG1, false}) +#define TMC4361A_SH_REG2_DMAX_MASK 0x00FFFFFF +#define TMC4361A_SH_REG2_DMAX_SHIFT 0 +#define TMC4361A_SH_REG2_DMAX_FIELD ((RegisterField) {TMC4361A_SH_REG2_DMAX_MASK, TMC4361A_SH_REG2_DMAX_SHIFT, TMC4361A_SH_REG2, false}) +#define TMC4361A_SH_REG3_ASTART_MASK 0x00FFFFFF +#define TMC4361A_SH_REG3_ASTART_SHIFT 0 +#define TMC4361A_SH_REG3_ASTART_FIELD ((RegisterField) {TMC4361A_SH_REG3_ASTART_MASK, TMC4361A_SH_REG3_ASTART_SHIFT, TMC4361A_SH_REG3, false}) +#define TMC4361A_SH_REG3_BOW1_MASK 0x00FFFFFF +#define TMC4361A_SH_REG3_BOW1_SHIFT 0 +#define TMC4361A_SH_REG3_BOW1_FIELD ((RegisterField) {TMC4361A_SH_REG3_BOW1_MASK, TMC4361A_SH_REG3_BOW1_SHIFT, TMC4361A_SH_REG3, false}) +#define TMC4361A_SH_REG4_DFINAL_MASK 0x00FFFFFF +#define TMC4361A_SH_REG4_DFINAL_SHIFT 0 +#define TMC4361A_SH_REG4_DFINAL_FIELD ((RegisterField) {TMC4361A_SH_REG4_DFINAL_MASK, TMC4361A_SH_REG4_DFINAL_SHIFT, TMC4361A_SH_REG4, false}) +#define TMC4361A_SH_REG4_BOW2_MASK 0x00FFFFFF +#define TMC4361A_SH_REG4_BOW2_SHIFT 0 +#define TMC4361A_SH_REG4_BOW2_FIELD ((RegisterField) {TMC4361A_SH_REG4_BOW2_MASK, TMC4361A_SH_REG4_BOW2_SHIFT, TMC4361A_SH_REG4, false}) +#define TMC4361A_SH_REG5_VBREAK_MASK 0x7FFFFFFF +#define TMC4361A_SH_REG5_VBREAK_SHIFT 0 +#define TMC4361A_SH_REG5_VBREAK_FIELD ((RegisterField) {TMC4361A_SH_REG5_VBREAK_MASK, TMC4361A_SH_REG5_VBREAK_SHIFT, TMC4361A_SH_REG5, false}) +#define TMC4361A_SH_REG5_BOW3_MASK 0x00FFFFFF +#define TMC4361A_SH_REG5_BOW3_SHIFT 0 +#define TMC4361A_SH_REG5_BOW3_FIELD ((RegisterField) {TMC4361A_SH_REG5_BOW3_MASK, TMC4361A_SH_REG5_BOW3_SHIFT, TMC4361A_SH_REG5, false}) +#define TMC4361A_SH_REG6_VSTART_MASK 0x7FFFFFFF +#define TMC4361A_SH_REG6_VSTART_SHIFT 0 +#define TMC4361A_SH_REG6_VSTART_FIELD ((RegisterField) {TMC4361A_SH_REG6_VSTART_MASK, TMC4361A_SH_REG6_VSTART_SHIFT, TMC4361A_SH_REG6, false}) +#define TMC4361A_SH_REG6_BOW4_MASK 0x00FFFFFF +#define TMC4361A_SH_REG6_BOW4_SHIFT 0 +#define TMC4361A_SH_REG6_BOW4_FIELD ((RegisterField) {TMC4361A_SH_REG6_BOW4_MASK, TMC4361A_SH_REG6_BOW4_SHIFT, TMC4361A_SH_REG6, false}) +#define TMC4361A_SH_REG6_VSTOP_MASK 0x7FFFFFFF +#define TMC4361A_SH_REG6_VSTOP_SHIFT 0 +#define TMC4361A_SH_REG6_VSTOP_FIELD ((RegisterField) {TMC4361A_SH_REG6_VSTOP_MASK, TMC4361A_SH_REG6_VSTOP_SHIFT, TMC4361A_SH_REG6, false}) +#define TMC4361A_SH_REG7_VSTOP_MASK 0xFFFFFFFF +#define TMC4361A_SH_REG7_VSTOP_SHIFT 0 +#define TMC4361A_SH_REG7_VSTOP_FIELD ((RegisterField) {TMC4361A_SH_REG7_VSTOP_MASK, TMC4361A_SH_REG7_VSTOP_SHIFT, TMC4361A_SH_REG7, false}) +#define TMC4361A_SH_REG7_VMAX_MASK 0xFFFFFFFF +#define TMC4361A_SH_REG7_VMAX_SHIFT 0 +#define TMC4361A_SH_REG7_VMAX_FIELD ((RegisterField) {TMC4361A_SH_REG7_VMAX_MASK, TMC4361A_SH_REG7_VMAX_SHIFT, TMC4361A_SH_REG7, true}) +#define TMC4361A_SH_REG8_BOW1_MASK 0x00FFFFFF +#define TMC4361A_SH_REG8_BOW1_SHIFT 0 +#define TMC4361A_SH_REG8_BOW1_FIELD ((RegisterField) {TMC4361A_SH_REG8_BOW1_MASK, TMC4361A_SH_REG8_BOW1_SHIFT, TMC4361A_SH_REG8, false}) +#define TMC4361A_SH_REG8_AMAX_MASK 0x00FFFFFF +#define TMC4361A_SH_REG8_AMAX_SHIFT 0 +#define TMC4361A_SH_REG8_AMAX_FIELD ((RegisterField) {TMC4361A_SH_REG8_AMAX_MASK, TMC4361A_SH_REG8_AMAX_SHIFT, TMC4361A_SH_REG8, false}) +#define TMC4361A_SH_REG9_BOW2_MASK 0x00FFFFFF +#define TMC4361A_SH_REG9_BOW2_SHIFT 0 +#define TMC4361A_SH_REG9_BOW2_FIELD ((RegisterField) {TMC4361A_SH_REG9_BOW2_MASK, TMC4361A_SH_REG9_BOW2_SHIFT, TMC4361A_SH_REG9, false}) +#define TMC4361A_SH_REG9_DMAX_MASK 0x00FFFFFF +#define TMC4361A_SH_REG9_DMAX_SHIFT 0 +#define TMC4361A_SH_REG9_DMAX_FIELD ((RegisterField) {TMC4361A_SH_REG9_DMAX_MASK, TMC4361A_SH_REG9_DMAX_SHIFT, TMC4361A_SH_REG9, false}) +#define TMC4361A_SH_REG10_BOW3_MASK 0x00FFFFFF +#define TMC4361A_SH_REG10_BOW3_SHIFT 0 +#define TMC4361A_SH_REG10_BOW3_FIELD ((RegisterField) {TMC4361A_SH_REG10_BOW3_MASK, TMC4361A_SH_REG10_BOW3_SHIFT, TMC4361A_SH_REG10, false}) +#define TMC4361A_SH_REG10_BOW1_MASK 0x00FFFFFF +#define TMC4361A_SH_REG10_BOW1_SHIFT 0 +#define TMC4361A_SH_REG10_BOW1_FIELD ((RegisterField) {TMC4361A_SH_REG10_BOW1_MASK, TMC4361A_SH_REG10_BOW1_SHIFT, TMC4361A_SH_REG10, false}) +#define TMC4361A_SH_REG10_ASTART_MASK 0x00FFFFFF +#define TMC4361A_SH_REG10_ASTART_SHIFT 0 +#define TMC4361A_SH_REG10_ASTART_FIELD ((RegisterField) {TMC4361A_SH_REG10_ASTART_MASK, TMC4361A_SH_REG10_ASTART_SHIFT, TMC4361A_SH_REG10, false}) +#define TMC4361A_SH_REG11_BOW4_MASK 0x00FFFFFF +#define TMC4361A_SH_REG11_BOW4_SHIFT 0 +#define TMC4361A_SH_REG11_BOW4_FIELD ((RegisterField) {TMC4361A_SH_REG11_BOW4_MASK, TMC4361A_SH_REG11_BOW4_SHIFT, TMC4361A_SH_REG11, false}) +#define TMC4361A_SH_REG11_BOW2_MASK 0x00FFFFFF +#define TMC4361A_SH_REG11_BOW2_SHIFT 0 +#define TMC4361A_SH_REG11_BOW2_FIELD ((RegisterField) {TMC4361A_SH_REG11_BOW2_MASK, TMC4361A_SH_REG11_BOW2_SHIFT, TMC4361A_SH_REG11, false}) +#define TMC4361A_SH_REG11_DFINAL_MASK 0x00FFFFFF +#define TMC4361A_SH_REG11_DFINAL_SHIFT 0 +#define TMC4361A_SH_REG11_DFINAL_FIELD ((RegisterField) {TMC4361A_SH_REG11_DFINAL_MASK, TMC4361A_SH_REG11_DFINAL_SHIFT, TMC4361A_SH_REG11, false}) +#define TMC4361A_SH_REG12_BOW3_MASK 0x00FFFFFF +#define TMC4361A_SH_REG12_BOW3_SHIFT 0 +#define TMC4361A_SH_REG12_BOW3_FIELD ((RegisterField) {TMC4361A_SH_REG12_BOW3_MASK, TMC4361A_SH_REG12_BOW3_SHIFT, TMC4361A_SH_REG12, false}) +#define TMC4361A_SH_REG12_VBREAK_MASK 0x7FFFFFFF +#define TMC4361A_SH_REG12_VBREAK_SHIFT 0 +#define TMC4361A_SH_REG12_VBREAK_FIELD ((RegisterField) {TMC4361A_SH_REG12_VBREAK_MASK, TMC4361A_SH_REG12_VBREAK_SHIFT, TMC4361A_SH_REG12, false}) +#define TMC4361A_SH_REG13_BOW4_MASK 0x00FFFFFF +#define TMC4361A_SH_REG13_BOW4_SHIFT 0 +#define TMC4361A_SH_REG13_BOW4_FIELD ((RegisterField) {TMC4361A_SH_REG13_BOW4_MASK, TMC4361A_SH_REG13_BOW4_SHIFT, TMC4361A_SH_REG13, false}) +#define TMC4361A_SH_REG13_VSTART_MASK 0x7FFFFFFF +#define TMC4361A_SH_REG13_VSTART_SHIFT 0 +#define TMC4361A_SH_REG13_VSTART_FIELD ((RegisterField) {TMC4361A_SH_REG13_VSTART_MASK, TMC4361A_SH_REG13_VSTART_SHIFT, TMC4361A_SH_REG13, false}) +#define TMC4361A_SH_REG13_VSTOP_MASK 0x7FFFFFFF +#define TMC4361A_SH_REG13_VSTOP_SHIFT 0 +#define TMC4361A_SH_REG13_VSTOP_FIELD ((RegisterField) {TMC4361A_SH_REG13_VSTOP_MASK, TMC4361A_SH_REG13_VSTOP_SHIFT, TMC4361A_SH_REG13, false}) +#define TMC4361A_DFREEZE_MASK 0x00FFFFFF +#define TMC4361A_DFREEZE_SHIFT 0 +#define TMC4361A_DFREEZE_FIELD ((RegisterField) {TMC4361A_DFREEZE_MASK, TMC4361A_DFREEZE_SHIFT, TMC4361A_Freeze Registers, false}) +#define TMC4361A_IFREEZE_MASK 0xFF000000 +#define TMC4361A_IFREEZE_SHIFT 24 +#define TMC4361A_IFREEZE_FIELD ((RegisterField) {TMC4361A_IFREEZE_MASK, TMC4361A_IFREEZE_SHIFT, TMC4361A_Freeze Registers, false}) +#define TMC4361A_CLK_GATING_REG_MASK 0x00000007 +#define TMC4361A_CLK_GATING_REG_SHIFT 0 +#define TMC4361A_CLK_GATING_REG_FIELD ((RegisterField) {TMC4361A_CLK_GATING_REG_MASK, TMC4361A_CLK_GATING_REG_SHIFT, TMC4361A_CLK_GATING_REG, false}) +#define TMC4361A_RESET_REG_MASK 0xFFFFFF00 +#define TMC4361A_RESET_REG_SHIFT 8 +#define TMC4361A_RESET_REG_FIELD ((RegisterField) {TMC4361A_RESET_REG_MASK, TMC4361A_RESET_REG_SHIFT, TMC4361A_RESET_REG, false}) +#define TMC4361A_ENC_POS_MASK 0xFFFFFFFF +#define TMC4361A_ENC_POS_SHIFT 0 +#define TMC4361A_ENC_POS_FIELD ((RegisterField) {TMC4361A_ENC_POS_MASK, TMC4361A_ENC_POS_SHIFT, TMC4361A_ENC_POS, true}) +#define TMC4361A_ENC_LATCH_MASK 0xFFFFFFFF +#define TMC4361A_ENC_LATCH_SHIFT 0 +#define TMC4361A_ENC_LATCH_FIELD ((RegisterField) {TMC4361A_ENC_LATCH_MASK, TMC4361A_ENC_LATCH_SHIFT, TMC4361A_ENC_LATCH, true}) +#define TMC4361A_ENC_RESET_VAL_MASK 0xFFFFFFFF +#define TMC4361A_ENC_RESET_VAL_SHIFT 0 +#define TMC4361A_ENC_RESET_VAL_FIELD ((RegisterField) {TMC4361A_ENC_RESET_VAL_MASK, TMC4361A_ENC_RESET_VAL_SHIFT, TMC4361A_ENC_RESET_VAL, true}) +#define TMC4361A_ENC_POS_DEV_MASK 0xFFFFFFFF +#define TMC4361A_ENC_POS_DEV_SHIFT 0 +#define TMC4361A_ENC_POS_DEV_FIELD ((RegisterField) {TMC4361A_ENC_POS_DEV_MASK, TMC4361A_ENC_POS_DEV_SHIFT, TMC4361A_ENC_POS_DEV, true}) +#define TMC4361A_CL_TR_TOLERANCE_MASK 0x7FFFFFFF +#define TMC4361A_CL_TR_TOLERANCE_SHIFT 0 +#define TMC4361A_CL_TR_TOLERANCE_FIELD ((RegisterField) {TMC4361A_CL_TR_TOLERANCE_MASK, TMC4361A_CL_TR_TOLERANCE_SHIFT, TMC4361A_CL_TR_TOLERANCE, false}) +#define TMC4361A_ENC_POS_DEV_TOL_MASK 0x7FFFFFFF +#define TMC4361A_ENC_POS_DEV_TOL_SHIFT 0 +#define TMC4361A_ENC_POS_DEV_TOL_FIELD ((RegisterField) {TMC4361A_ENC_POS_DEV_TOL_MASK, TMC4361A_ENC_POS_DEV_TOL_SHIFT, TMC4361A_ENC_POS_DEV_TOL, false}) +#define TMC4361A_ENC_CONST_MASK 0x7FFFFFFF +#define TMC4361A_ENC_CONST_SHIFT 0 +#define TMC4361A_ENC_CONST_FIELD ((RegisterField) {TMC4361A_ENC_CONST_MASK, TMC4361A_ENC_CONST_SHIFT, TMC4361A_ENC_CONST, false}) +#define TMC4361A_ENC_IN_RES_MASK 0x7FFFFFFF +#define TMC4361A_ENC_IN_RES_SHIFT 0 +#define TMC4361A_ENC_IN_RES_FIELD ((RegisterField) {TMC4361A_ENC_IN_RES_MASK, TMC4361A_ENC_IN_RES_SHIFT, TMC4361A_ENC_IN_RES, false}) +#define TMC4361A_MANUAL_ENC_CONST_MASK 0x80000000 +#define TMC4361A_MANUAL_ENC_CONST_SHIFT 31 +#define TMC4361A_MANUAL_ENC_CONST_FIELD ((RegisterField) {TMC4361A_MANUAL_ENC_CONST_MASK, TMC4361A_MANUAL_ENC_CONST_SHIFT, TMC4361A_manual_enc_const, false}) +#define TMC4361A_ENC_OUT_RES_MASK 0x7FFFFFFF +#define TMC4361A_ENC_OUT_RES_SHIFT 0 +#define TMC4361A_ENC_OUT_RES_FIELD ((RegisterField) {TMC4361A_ENC_OUT_RES_MASK, TMC4361A_ENC_OUT_RES_SHIFT, TMC4361A_ENC_OUT_RES, false}) +#define TMC4361A_SER_CLK_IN_HIGH_MASK 0x0000FFFF +#define TMC4361A_SER_CLK_IN_HIGH_SHIFT 0 +#define TMC4361A_SER_CLK_IN_HIGH_FIELD ((RegisterField) {TMC4361A_SER_CLK_IN_HIGH_MASK, TMC4361A_SER_CLK_IN_HIGH_SHIFT, TMC4361A_SER_CLK_IN_HIGH, false}) +#define TMC4361A_SER_CLK_IN_LOW_MASK 0xFFFF0000 +#define TMC4361A_SER_CLK_IN_LOW_SHIFT 16 +#define TMC4361A_SER_CLK_IN_LOW_FIELD ((RegisterField) {TMC4361A_SER_CLK_IN_LOW_MASK, TMC4361A_SER_CLK_IN_LOW_SHIFT, TMC4361A_SER_CLK_IN_LOW, false}) +#define TMC4361A_SSI_IN_CLK_DELAY_MASK 0x0000FFFF +#define TMC4361A_SSI_IN_CLK_DELAY_SHIFT 0 +#define TMC4361A_SSI_IN_CLK_DELAY_FIELD ((RegisterField) {TMC4361A_SSI_IN_CLK_DELAY_MASK, TMC4361A_SSI_IN_CLK_DELAY_SHIFT, TMC4361A_SSI_IN_CLK_DELAY, false}) +#define TMC4361A_SSI_IN_WTIME_MASK 0xFFFF0000 +#define TMC4361A_SSI_IN_WTIME_SHIFT 16 +#define TMC4361A_SSI_IN_WTIME_FIELD ((RegisterField) {TMC4361A_SSI_IN_WTIME_MASK, TMC4361A_SSI_IN_WTIME_SHIFT, TMC4361A_SSI_IN_WTIME, false}) +#define TMC4361A_SER_PTIME_MASK 0x000FFFFF +#define TMC4361A_SER_PTIME_SHIFT 0 +#define TMC4361A_SER_PTIME_FIELD ((RegisterField) {TMC4361A_SER_PTIME_MASK, TMC4361A_SER_PTIME_SHIFT, TMC4361A_SER_PTIME, false}) +#define TMC4361A_CL_OFFSET_MASK 0xFFFFFFFF +#define TMC4361A_CL_OFFSET_SHIFT 0 +#define TMC4361A_CL_OFFSET_FIELD ((RegisterField) {TMC4361A_CL_OFFSET_MASK, TMC4361A_CL_OFFSET_SHIFT, TMC4361A_CL_OFFSET, true}) +#define TMC4361A_PID_VEL_MASK 0xFFFFFFFF +#define TMC4361A_PID_VEL_SHIFT 0 +#define TMC4361A_PID_VEL_FIELD ((RegisterField) {TMC4361A_PID_VEL_MASK, TMC4361A_PID_VEL_SHIFT, TMC4361A_PID_VEL, true}) +#define TMC4361A_CL_VMAX_CALC_P_MASK 0x00FFFFFF +#define TMC4361A_CL_VMAX_CALC_P_SHIFT 0 +#define TMC4361A_CL_VMAX_CALC_P_FIELD ((RegisterField) {TMC4361A_CL_VMAX_CALC_P_MASK, TMC4361A_CL_VMAX_CALC_P_SHIFT, TMC4361A_CL_VMAX_CALC_P, false}) +#define TMC4361A_PID_P_MASK 0x00FFFFFF +#define TMC4361A_PID_P_SHIFT 0 +#define TMC4361A_PID_P_FIELD ((RegisterField) {TMC4361A_PID_P_MASK, TMC4361A_PID_P_SHIFT, TMC4361A_PID_P, false}) +#define TMC4361A_PID_ISUM_RD_MASK 0xFFFFFFFF +#define TMC4361A_PID_ISUM_RD_SHIFT 0 +#define TMC4361A_PID_ISUM_RD_FIELD ((RegisterField) {TMC4361A_PID_ISUM_RD_MASK, TMC4361A_PID_ISUM_RD_SHIFT, TMC4361A_PID_ISUM_RD, true}) +#define TMC4361A_CL_VMAX_CALC_I_MASK 0x00FFFFFF +#define TMC4361A_CL_VMAX_CALC_I_SHIFT 0 +#define TMC4361A_CL_VMAX_CALC_I_FIELD ((RegisterField) {TMC4361A_CL_VMAX_CALC_I_MASK, TMC4361A_CL_VMAX_CALC_I_SHIFT, TMC4361A_CL_VMAX_CALC_I, false}) +#define TMC4361A_PID_I_MASK 0x00FFFFFF +#define TMC4361A_PID_I_SHIFT 0 +#define TMC4361A_PID_I_FIELD ((RegisterField) {TMC4361A_PID_I_MASK, TMC4361A_PID_I_SHIFT, TMC4361A_PID_I, false}) +#define TMC4361A_CL_DELTA_P_MASK 0x00FFFFFF +#define TMC4361A_CL_DELTA_P_SHIFT 0 +#define TMC4361A_CL_DELTA_P_FIELD ((RegisterField) {TMC4361A_CL_DELTA_P_MASK, TMC4361A_CL_DELTA_P_SHIFT, TMC4361A_CL_DELTA_P, false}) +#define TMC4361A_PID_D_MASK 0x00FFFFFF +#define TMC4361A_PID_D_SHIFT 0 +#define TMC4361A_PID_D_FIELD ((RegisterField) {TMC4361A_PID_D_MASK, TMC4361A_PID_D_SHIFT, TMC4361A_PID_D, false}) +#define TMC4361A_PID_E_MASK 0xFFFFFFFF +#define TMC4361A_PID_E_SHIFT 0 +#define TMC4361A_PID_E_FIELD ((RegisterField) {TMC4361A_PID_E_MASK, TMC4361A_PID_E_SHIFT, TMC4361A_PID_E, true}) +#define TMC4361A_PID_I_CLIP_MASK 0x00007FFF +#define TMC4361A_PID_I_CLIP_SHIFT 0 +#define TMC4361A_PID_I_CLIP_FIELD ((RegisterField) {TMC4361A_PID_I_CLIP_MASK, TMC4361A_PID_I_CLIP_SHIFT, TMC4361A_PID_I_CLIP, false}) +#define TMC4361A_PID_D_CLKDIV_MASK 0x00FF0000 +#define TMC4361A_PID_D_CLKDIV_SHIFT 16 +#define TMC4361A_PID_D_CLKDIV_FIELD ((RegisterField) {TMC4361A_PID_D_CLKDIV_MASK, TMC4361A_PID_D_CLKDIV_SHIFT, TMC4361A_PID_D_CLKDIV, false}) +#define TMC4361A_PID_DV_CLIP_MASK 0x7FFFFFFF +#define TMC4361A_PID_DV_CLIP_SHIFT 0 +#define TMC4361A_PID_DV_CLIP_FIELD ((RegisterField) {TMC4361A_PID_DV_CLIP_MASK, TMC4361A_PID_DV_CLIP_SHIFT, TMC4361A_PID_DV_CLIP, false}) +#define TMC4361A_CL_TOLERANCE_MASK 0x000000FF +#define TMC4361A_CL_TOLERANCE_SHIFT 0 +#define TMC4361A_CL_TOLERANCE_FIELD ((RegisterField) {TMC4361A_CL_TOLERANCE_MASK, TMC4361A_CL_TOLERANCE_SHIFT, TMC4361A_CL_TOLERANCE, false}) +#define TMC4361A_PID_TOLERANCE_MASK 0x000FFFFF +#define TMC4361A_PID_TOLERANCE_SHIFT 0 +#define TMC4361A_PID_TOLERANCE_FIELD ((RegisterField) {TMC4361A_PID_TOLERANCE_MASK, TMC4361A_PID_TOLERANCE_SHIFT, TMC4361A_PID_TOLERANCE, false}) +#define TMC4361A_FS_VEL_MASK 0x00FFFFFF +#define TMC4361A_FS_VEL_SHIFT 0 +#define TMC4361A_FS_VEL_FIELD ((RegisterField) {TMC4361A_FS_VEL_MASK, TMC4361A_FS_VEL_SHIFT, TMC4361A_FS_VEL, false}) +#define TMC4361A_DC_VEL_MASK 0x00FFFFFF +#define TMC4361A_DC_VEL_SHIFT 0 +#define TMC4361A_DC_VEL_FIELD ((RegisterField) {TMC4361A_DC_VEL_MASK, TMC4361A_DC_VEL_SHIFT, TMC4361A_DC_VEL, false}) +#define TMC4361A_CL_VMIN_EMF_MASK 0x00FFFFFF +#define TMC4361A_CL_VMIN_EMF_SHIFT 0 +#define TMC4361A_CL_VMIN_EMF_FIELD ((RegisterField) {TMC4361A_CL_VMIN_EMF_MASK, TMC4361A_CL_VMIN_EMF_SHIFT, TMC4361A_CL_VMIN_EMF, false}) +#define TMC4361A_DC_TIME_MASK 0x000000FF +#define TMC4361A_DC_TIME_SHIFT 0 +#define TMC4361A_DC_TIME_FIELD ((RegisterField) {TMC4361A_DC_TIME_MASK, TMC4361A_DC_TIME_SHIFT, TMC4361A_DC_TIME, false}) +#define TMC4361A_DC_SG_MASK 0x0000FF00 +#define TMC4361A_DC_SG_SHIFT 8 +#define TMC4361A_DC_SG_FIELD ((RegisterField) {TMC4361A_DC_SG_MASK, TMC4361A_DC_SG_SHIFT, TMC4361A_DC_SG, false}) +#define TMC4361A_DC_BLKTIME_MASK 0xFFFF0000 +#define TMC4361A_DC_BLKTIME_SHIFT 16 +#define TMC4361A_DC_BLKTIME_FIELD ((RegisterField) {TMC4361A_DC_BLKTIME_MASK, TMC4361A_DC_BLKTIME_SHIFT, TMC4361A_DC_BLKTIME, false}) +#define TMC4361A_CL_VADD_EMF_MASK 0x00FFFFFF +#define TMC4361A_CL_VADD_EMF_SHIFT 0 +#define TMC4361A_CL_VADD_EMF_FIELD ((RegisterField) {TMC4361A_CL_VADD_EMF_MASK, TMC4361A_CL_VADD_EMF_SHIFT, TMC4361A_CL_VADD_EMF, false}) +#define TMC4361A_DC_LSPTM_MASK 0xFFFFFFFF +#define TMC4361A_DC_LSPTM_SHIFT 0 +#define TMC4361A_DC_LSPTM_FIELD ((RegisterField) {TMC4361A_DC_LSPTM_MASK, TMC4361A_DC_LSPTM_SHIFT, TMC4361A_DC_LSPTM, false}) +#define TMC4361A_ENC_VEL_ZERO_MASK 0x00FFFFFF +#define TMC4361A_ENC_VEL_ZERO_SHIFT 0 +#define TMC4361A_ENC_VEL_ZERO_FIELD ((RegisterField) {TMC4361A_ENC_VEL_ZERO_MASK, TMC4361A_ENC_VEL_ZERO_SHIFT, TMC4361A_ENC_VEL_ZERO, false}) +#define TMC4361A_ENC_VMEAN_WAIT_MASK 0x000000FF +#define TMC4361A_ENC_VMEAN_WAIT_SHIFT 0 +#define TMC4361A_ENC_VMEAN_WAIT_FIELD ((RegisterField) {TMC4361A_ENC_VMEAN_WAIT_MASK, TMC4361A_ENC_VMEAN_WAIT_SHIFT, TMC4361A_ENC_VMEAN_WAIT, false}) +#define TMC4361A_ENC_VMEAN_FILTER_MASK 0x00000F00 +#define TMC4361A_ENC_VMEAN_FILTER_SHIFT 8 +#define TMC4361A_ENC_VMEAN_FILTER_FIELD ((RegisterField) {TMC4361A_ENC_VMEAN_FILTER_MASK, TMC4361A_ENC_VMEAN_FILTER_SHIFT, TMC4361A_ENC_VMEAN_FILTER, false}) +#define TMC4361A_ENC_VMEAN_INT_MASK 0xFFFF0000 +#define TMC4361A_ENC_VMEAN_INT_SHIFT 16 +#define TMC4361A_ENC_VMEAN_INT_FIELD ((RegisterField) {TMC4361A_ENC_VMEAN_INT_MASK, TMC4361A_ENC_VMEAN_INT_SHIFT, TMC4361A_ENC_VMEAN_INT, false}) +#define TMC4361A_SER_ENC_VARIATION_MASK 0x000000FF +#define TMC4361A_SER_ENC_VARIATION_SHIFT 0 +#define TMC4361A_SER_ENC_VARIATION_FIELD ((RegisterField) {TMC4361A_SER_ENC_VARIATION_MASK, TMC4361A_SER_ENC_VARIATION_SHIFT, TMC4361A_SER_ENC_VARIATION, false}) +#define TMC4361A_CL_CYCLE_MASK 0xFFFF0000 +#define TMC4361A_CL_CYCLE_SHIFT 16 +#define TMC4361A_CL_CYCLE_FIELD ((RegisterField) {TMC4361A_CL_CYCLE_MASK, TMC4361A_CL_CYCLE_SHIFT, TMC4361A_CL_CYCLE, false}) +#define TMC4361A_V_ENC_MASK 0xFFFFFFFF +#define TMC4361A_V_ENC_SHIFT 0 +#define TMC4361A_V_ENC_FIELD ((RegisterField) {TMC4361A_V_ENC_MASK, TMC4361A_V_ENC_SHIFT, TMC4361A_V_ENC, true}) +#define TMC4361A_V_ENC_MEAN_MASK 0xFFFFFFFF +#define TMC4361A_V_ENC_MEAN_SHIFT 0 +#define TMC4361A_V_ENC_MEAN_FIELD ((RegisterField) {TMC4361A_V_ENC_MEAN_MASK, TMC4361A_V_ENC_MEAN_SHIFT, TMC4361A_V_ENC_MEAN, true}) +#define TMC4361A_VSTALL_LIMIT_MASK 0x00FFFFFF +#define TMC4361A_VSTALL_LIMIT_SHIFT 0 +#define TMC4361A_VSTALL_LIMIT_FIELD ((RegisterField) {TMC4361A_VSTALL_LIMIT_MASK, TMC4361A_VSTALL_LIMIT_SHIFT, TMC4361A_VSTALL_LIMIT, false}) +#define TMC4361A_ADDR_TO_ENC_MASK 0xFFFFFFFF +#define TMC4361A_ADDR_TO_ENC_SHIFT 0 +#define TMC4361A_ADDR_TO_ENC_FIELD ((RegisterField) {TMC4361A_ADDR_TO_ENC_MASK, TMC4361A_ADDR_TO_ENC_SHIFT, TMC4361A_ADDR_TO_ENC, false}) +#define TMC4361A_DATA_TO_ENC_MASK 0xFFFFFFFF +#define TMC4361A_DATA_TO_ENC_SHIFT 0 +#define TMC4361A_DATA_TO_ENC_FIELD ((RegisterField) {TMC4361A_DATA_TO_ENC_MASK, TMC4361A_DATA_TO_ENC_SHIFT, TMC4361A_DATA_TO_ENC, false}) +#define TMC4361A_ADDR_FROM_ENC_MASK 0xFFFFFFFF +#define TMC4361A_ADDR_FROM_ENC_SHIFT 0 +#define TMC4361A_ADDR_FROM_ENC_FIELD ((RegisterField) {TMC4361A_ADDR_FROM_ENC_MASK, TMC4361A_ADDR_FROM_ENC_SHIFT, TMC4361A_ADDR_FROM_ENC, false}) +#define TMC4361A_DATA_FROM_ENC_MASK 0xFFFFFFFF +#define TMC4361A_DATA_FROM_ENC_SHIFT 0 +#define TMC4361A_DATA_FROM_ENC_FIELD ((RegisterField) {TMC4361A_DATA_FROM_ENC_MASK, TMC4361A_DATA_FROM_ENC_SHIFT, TMC4361A_DATA_FROM_ENC, false}) +#define TMC4361A_POLLING_STATUS_MASK 0xFFFFFFFF +#define TMC4361A_POLLING_STATUS_SHIFT 0 +#define TMC4361A_POLLING_STATUS_FIELD ((RegisterField) {TMC4361A_POLLING_STATUS_MASK, TMC4361A_POLLING_STATUS_SHIFT, TMC4361A_POLLING_STATUS, false}) +#define TMC4361A_COVER_LOW_MASK 0xFFFFFFFF +#define TMC4361A_COVER_LOW_SHIFT 0 +#define TMC4361A_COVER_LOW_FIELD ((RegisterField) {TMC4361A_COVER_LOW_MASK, TMC4361A_COVER_LOW_SHIFT, TMC4361A_COVER_LOW, false}) +#define TMC4361A_POLLING_REG_GSTAT_MASK 0xF0000000 +#define TMC4361A_POLLING_REG_GSTAT_SHIFT 28 +#define TMC4361A_POLLING_REG_GSTAT_FIELD ((RegisterField) {TMC4361A_POLLING_REG_GSTAT_MASK, TMC4361A_POLLING_REG_GSTAT_SHIFT, TMC4361A_POLLING_REG_GSTAT, false}) +#define TMC4361A_POLLING_REG_PWM_SCALE_MASK 0x0FF00000 +#define TMC4361A_POLLING_REG_PWM_SCALE_SHIFT 20 +#define TMC4361A_POLLING_REG_PWM_SCALE_FIELD ((RegisterField) {TMC4361A_POLLING_REG_PWM_SCALE_MASK, TMC4361A_POLLING_REG_PWM_SCALE_SHIFT, TMC4361A_POLLING_REG_PWM_SCALE, false}) +#define TMC4361A_POLLING_REG_LOST_STEPS_MASK 0xFFFFFFFF +#define TMC4361A_POLLING_REG_LOST_STEPS_SHIFT 0 +#define TMC4361A_POLLING_REG_LOST_STEPS_FIELD ((RegisterField) {TMC4361A_POLLING_REG_LOST_STEPS_MASK, TMC4361A_POLLING_REG_LOST_STEPS_SHIFT, TMC4361A_POLLING_REG_LOST_STEPS, false}) +#define TMC4361A_COVER_HIGH_MASK 0xFFFFFFFF +#define TMC4361A_COVER_HIGH_SHIFT 0 +#define TMC4361A_COVER_HIGH_FIELD ((RegisterField) {TMC4361A_COVER_HIGH_MASK, TMC4361A_COVER_HIGH_SHIFT, TMC4361A_COVER_HIGH, false}) +#define TMC4361A_COVER_DRV_LOW_MASK 0xFFFFFFFF +#define TMC4361A_COVER_DRV_LOW_SHIFT 0 +#define TMC4361A_COVER_DRV_LOW_FIELD ((RegisterField) {TMC4361A_COVER_DRV_LOW_MASK, TMC4361A_COVER_DRV_LOW_SHIFT, TMC4361A_COVER_DRV_LOW, false}) +#define TMC4361A_COVER_DRV_HIGH_MASK 0xFFFFFFFF +#define TMC4361A_COVER_DRV_HIGH_SHIFT 0 +#define TMC4361A_COVER_DRV_HIGH_FIELD ((RegisterField) {TMC4361A_COVER_DRV_HIGH_MASK, TMC4361A_COVER_DRV_HIGH_SHIFT, TMC4361A_COVER_DRV_HIGH, false}) +#define TMC4361A_MSLUT_0_MASK 0xFFFFFFFF +#define TMC4361A_MSLUT_0_SHIFT 0 +#define TMC4361A_MSLUT_0_FIELD ((RegisterField) {TMC4361A_MSLUT_0_MASK, TMC4361A_MSLUT_0_SHIFT, TMC4361A_MSLUT_0, false}) +#define TMC4361A_MSLUT_1_MASK 0xFFFFFFFF +#define TMC4361A_MSLUT_1_SHIFT 0 +#define TMC4361A_MSLUT_1_FIELD ((RegisterField) {TMC4361A_MSLUT_1_MASK, TMC4361A_MSLUT_1_SHIFT, TMC4361A_MSLUT_1, false}) +#define TMC4361A_MSLUT_2_MASK 0xFFFFFFFF +#define TMC4361A_MSLUT_2_SHIFT 0 +#define TMC4361A_MSLUT_2_FIELD ((RegisterField) {TMC4361A_MSLUT_2_MASK, TMC4361A_MSLUT_2_SHIFT, TMC4361A_MSLUT_2, false}) +#define TMC4361A_MSLUT_3_MASK 0xFFFFFFFF +#define TMC4361A_MSLUT_3_SHIFT 0 +#define TMC4361A_MSLUT_3_FIELD ((RegisterField) {TMC4361A_MSLUT_3_MASK, TMC4361A_MSLUT_3_SHIFT, TMC4361A_MSLUT_3, false}) +#define TMC4361A_MSLUT_4_MASK 0xFFFFFFFF +#define TMC4361A_MSLUT_4_SHIFT 0 +#define TMC4361A_MSLUT_4_FIELD ((RegisterField) {TMC4361A_MSLUT_4_MASK, TMC4361A_MSLUT_4_SHIFT, TMC4361A_MSLUT_4, false}) +#define TMC4361A_MSLUT_5_MASK 0xFFFFFFFF +#define TMC4361A_MSLUT_5_SHIFT 0 +#define TMC4361A_MSLUT_5_FIELD ((RegisterField) {TMC4361A_MSLUT_5_MASK, TMC4361A_MSLUT_5_SHIFT, TMC4361A_MSLUT_5, false}) +#define TMC4361A_MSLUT_6_MASK 0xFFFFFFFF +#define TMC4361A_MSLUT_6_SHIFT 0 +#define TMC4361A_MSLUT_6_FIELD ((RegisterField) {TMC4361A_MSLUT_6_MASK, TMC4361A_MSLUT_6_SHIFT, TMC4361A_MSLUT_6, false}) +#define TMC4361A_MSLUT_7_MASK 0xFFFFFFFF +#define TMC4361A_MSLUT_7_SHIFT 0 +#define TMC4361A_MSLUT_7_FIELD ((RegisterField) {TMC4361A_MSLUT_7_MASK, TMC4361A_MSLUT_7_SHIFT, TMC4361A_MSLUT_7, false}) +#define TMC4361A_MSLUTSEL_MASK 0xFFFFFFFF +#define TMC4361A_MSLUTSEL_SHIFT 0 +#define TMC4361A_MSLUTSEL_FIELD ((RegisterField) {TMC4361A_MSLUTSEL_MASK, TMC4361A_MSLUTSEL_SHIFT, TMC4361A_MSLUTSEL, false}) +#define TMC4361A_MSCNT_MASK 0x000003FF +#define TMC4361A_MSCNT_SHIFT 0 +#define TMC4361A_MSCNT_FIELD ((RegisterField) {TMC4361A_MSCNT_MASK, TMC4361A_MSCNT_SHIFT, TMC4361A_MSCNT, false}) +#define TMC4361A_MSOFFSET_MASK 0x000003FF +#define TMC4361A_MSOFFSET_SHIFT 0 +#define TMC4361A_MSOFFSET_FIELD ((RegisterField) {TMC4361A_MSOFFSET_MASK, TMC4361A_MSOFFSET_SHIFT, TMC4361A_MSOFFSET, false}) +#define TMC4361A_CURRENTA_MASK 0x000001FF +#define TMC4361A_CURRENTA_SHIFT 0 +#define TMC4361A_CURRENTA_FIELD ((RegisterField) {TMC4361A_CURRENTA_MASK, TMC4361A_CURRENTA_SHIFT, TMC4361A_CURRENTA, true}) +#define TMC4361A_CURRENTB_MASK 0x01FF0000 +#define TMC4361A_CURRENTB_SHIFT 16 +#define TMC4361A_CURRENTB_FIELD ((RegisterField) {TMC4361A_CURRENTB_MASK, TMC4361A_CURRENTB_SHIFT, TMC4361A_CURRENTB, true}) +#define TMC4361A_CURRENTA_SPI_MASK 0x000001FF +#define TMC4361A_CURRENTA_SPI_SHIFT 0 +#define TMC4361A_CURRENTA_SPI_FIELD ((RegisterField) {TMC4361A_CURRENTA_SPI_MASK, TMC4361A_CURRENTA_SPI_SHIFT, TMC4361A_CURRENTA_SPI, true}) +#define TMC4361A_CURRENTB_SPI_MASK 0x01FF0000 +#define TMC4361A_CURRENTB_SPI_SHIFT 16 +#define TMC4361A_CURRENTB_SPI_FIELD ((RegisterField) {TMC4361A_CURRENTB_SPI_MASK, TMC4361A_CURRENTB_SPI_SHIFT, TMC4361A_CURRENTB_SPI, true}) +#define TMC4361A_TZEROWAIT_MASK 0xFFFFFFFF +#define TMC4361A_TZEROWAIT_SHIFT 0 +#define TMC4361A_TZEROWAIT_FIELD ((RegisterField) {TMC4361A_TZEROWAIT_MASK, TMC4361A_TZEROWAIT_SHIFT, TMC4361A_TZEROWAIT, false}) +#define TMC4361A_SCALE_PARAM_MASK 0x000001FF +#define TMC4361A_SCALE_PARAM_SHIFT 0 +#define TMC4361A_SCALE_PARAM_FIELD ((RegisterField) {TMC4361A_SCALE_PARAM_MASK, TMC4361A_SCALE_PARAM_SHIFT, TMC4361A_SCALE_PARAM, false}) +#define TMC4361A_CIRCULAR_DEC_MASK 0xFFFFFFFF +#define TMC4361A_CIRCULAR_DEC_SHIFT 0 +#define TMC4361A_CIRCULAR_DEC_FIELD ((RegisterField) {TMC4361A_CIRCULAR_DEC_MASK, TMC4361A_CIRCULAR_DEC_SHIFT, TMC4361A_CIRCULAR_DEC, false}) +#define TMC4361A_ENC_COMP_XOFFSET_MASK 0x0000FFFF +#define TMC4361A_ENC_COMP_XOFFSET_SHIFT 0 +#define TMC4361A_ENC_COMP_XOFFSET_FIELD ((RegisterField) {TMC4361A_ENC_COMP_XOFFSET_MASK, TMC4361A_ENC_COMP_XOFFSET_SHIFT, TMC4361A_ENC_COMP_XOFFSET, false}) +#define TMC4361A_ENC_COMP_YOFFSET_MASK 0x00FF0000 +#define TMC4361A_ENC_COMP_YOFFSET_SHIFT 16 +#define TMC4361A_ENC_COMP_YOFFSET_FIELD ((RegisterField) {TMC4361A_ENC_COMP_YOFFSET_MASK, TMC4361A_ENC_COMP_YOFFSET_SHIFT, TMC4361A_ENC_COMP_YOFFSET, true}) +#define TMC4361A_START_SIN_MASK 0x000000FF +#define TMC4361A_START_SIN_SHIFT 0 +#define TMC4361A_START_SIN_FIELD ((RegisterField) {TMC4361A_START_SIN_MASK, TMC4361A_START_SIN_SHIFT, TMC4361A_START_SIN, false}) +#define TMC4361A_START_SIN90_120_MASK 0x00FF0000 +#define TMC4361A_START_SIN90_120_SHIFT 16 +#define TMC4361A_START_SIN90_120_FIELD ((RegisterField) {TMC4361A_START_SIN90_120_MASK, TMC4361A_START_SIN90_120_SHIFT, TMC4361A_START_SIN90_120, false}) +#define TMC4361A_DAC_OFFSET_MASK 0xFF000000 +#define TMC4361A_DAC_OFFSET_SHIFT 24 +#define TMC4361A_DAC_OFFSET_FIELD ((RegisterField) {TMC4361A_DAC_OFFSET_MASK, TMC4361A_DAC_OFFSET_SHIFT, TMC4361A_DAC_OFFSET, false}) +#define TMC4361A_VERSION_NO_MASK 0x0000000F +#define TMC4361A_VERSION_NO_SHIFT 0 +#define TMC4361A_VERSION_NO_FIELD ((RegisterField) {TMC4361A_VERSION_NO_MASK, TMC4361A_VERSION_NO_SHIFT, TMC4361A_VERSION_NO, true}) + +#endif /* TMC4361A_HW_ABSTRACTION_H */ diff --git a/firmware/octoaxes/tmc/motion/MotorControl.cpp b/firmware/octoaxes/tmc/motion/MotorControl.cpp new file mode 100644 index 000000000..3c5b6321c --- /dev/null +++ b/firmware/octoaxes/tmc/motion/MotorControl.cpp @@ -0,0 +1,1405 @@ +/* + * MotorControl.cpp + * + * High-level motion control layer implementation. + * + * Created: 2026-01-21 + */ + +#include "MotorControl.h" +#include "../ic/TMC4361A/TMC4361A.h" +#include "../ic/TMC2660/TMC2660.h" +#include "../ic/TMC2240/TMC2240.h" +#include "../hal/TMC_SPI.h" +#include +#include "../../build_opt.h" + +// ============================================================================ +// Debug Helper +// ============================================================================ + +extern "C" void motor_debugPrint(const char* msg, int32_t val) +{ + DEBUG_PRINT(msg); + DEBUG_PRINT(":"); + DEBUG_PRINTLN(val); +} + +// ============================================================================ +// TMC2240 HAL Callbacks +// ============================================================================ + +// TMC2240 communicates through the TMC4361A 40-bit Cover interface +// tmc2240_readWriteSPI callback: a 5-byte SPI frame -> TMC4361A COVER_HIGH + COVER_LOW +extern "C" void tmc2240_readWriteSPI(uint16_t icID, uint8_t *data, size_t dataLength) +{ + // route to the TMC4361A Cover interface (5 bytes = 40-bit) + tmc4361A_readWriteCover(icID, data, dataLength); +} + +extern "C" TMC2240BusType tmc2240_getBusType(uint16_t icID) +{ + (void)icID; + return TMC2240_BUS_SPI; +} + +// ============================================================================ +// Motor Parameters Cache +// ============================================================================ + +MotorParams motorParams[MOTOR_IC_COUNT] = {}; + +// ============================================================================ +// Internal Helper Functions +// ============================================================================ + +// BOW parameter maximum (consistent with the old API BOWMAX) +#define BOWMAX ((1 << 24) - 1) + +// automatically compute the BOW parameters (exactly the same as the old API tmc4361A_adjustBows) +// formula: BOW = AMAX^2 / VMAX +// purpose: minimize the time spent saturated at AMAX +static void motor_adjustBows(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT || !motorParams[icID].initialized) + return; + + // get AMAX and VMAX (internal units) + int32_t vmax = motorParams[icID].vmax; + uint32_t amax = motorParams[icID].amax; + + if (vmax == 0) { + // avoid division by zero + motorParams[icID].bow1 = 0; + motorParams[icID].bow2 = 0; + motorParams[icID].bow3 = 0; + motorParams[icID].bow4 = 0; + return; + } + + // convert to mm units for the calculation (consistent with the old API) + // VMAX internal units are 24.8 fixed-point, so divide by 256 + float vmax_mm = (float)vmax / 256.0f / motorParams[icID].stepsPerMM; + + // AMAX internal units are 22.2 fixed-point, so divide by 4 + float amax_mm = (float)amax / 4.0f / motorParams[icID].stepsPerMM; + + // compute the BOW value: AMAX^2 / VMAX + float bowval_mm = (amax_mm * amax_mm) / vmax_mm; + + // convert back to internal units (microsteps) + int32_t bow = (int32_t)(bowval_mm * motorParams[icID].stepsPerMM); + if (bow < 0) bow = -bow; // abs + if (bow > BOWMAX) bow = BOWMAX; + + // set all 4 BOW parameters to the same value (consistent with the old API) + motorParams[icID].bow1 = bow; + motorParams[icID].bow2 = bow; + motorParams[icID].bow3 = bow; + motorParams[icID].bow4 = bow; + + DEBUG_PRINT("motor_adjustBows: icID="); + DEBUG_PRINT(icID); + DEBUG_PRINT(" AMAX="); + DEBUG_PRINT(amax); + DEBUG_PRINT(" VMAX="); + DEBUG_PRINT(vmax); + DEBUG_PRINT(" BOW="); + DEBUG_PRINTLN(bow); +} + +// Calculate current scale from peak current (mA) and sense resistor +// TMC2660 formula: +// I_peak = (CS + 1) / 32 × V_FS / R_sense +// I_rms = I_peak / √2 +// VSENSE=0 → V_FS = 0.310V; VSENSE=1 → V_FS = 0.165V +// this project's DRVCONF sets VSENSE=0 (high range) +// chip absolute max: 4A peak (2.8A RMS), CS range 0~31 +static uint8_t calculateCurrentScale(float currentMA, float rSense) +{ + // 2026-05-11 fix: interpret currentMA as RMS (consistent with legacy Squid firmware) + // going from legacy Squid software -> octoaxes firmware, interpreting it as PEAK would make the actual current ~30% low + // -> step loss and noise on the Y-axis acceleration phase (see SESSION.md 2026-05-11 #6) + // + // TMC2660 datasheet: I_peak = (CS+1)/32 × V_FS/R_sense + // derived from I_RMS = I_peak/sqrt(2): CS = I_RMS * R_sense * 32 * sqrt(2) / V_FS - 1 + // V_FS = 0.310 V (VSENSE=0, high range, consistent with the DRVCONF setting) + static const float SQRT2 = 1.41421356f; + float cs = (currentMA / 1000.0f) * rSense * 32.0f * SQRT2 / 0.310f - 1.0f; + + if (cs < 0) cs = 0; + if (cs > 31) cs = 31; + + return (uint8_t)cs; +} + +// Calculate microstep resolution register value (reserved for future use) +__attribute__((unused)) +static uint8_t calculateMresValue(uint16_t microsteps) +{ + // MRES: 0=256, 1=128, 2=64, 3=32, 4=16, 5=8, 6=4, 7=2, 8=1 + switch (microsteps) { + case 256: return 0; + case 128: return 1; + case 64: return 2; + case 32: return 3; + case 16: return 4; + case 8: return 5; + case 4: return 6; + case 2: return 7; + case 1: return 8; + default: return 0; // Default to 256 + } +} + +// Get TMC2240 full-scale current (A) from CURRENT_RANGE setting +// TMC2240 uses integrated current sense (ICS), no external sense resistor +// I_FS is determined by DRV_CONF.CURRENT_RANGE (assuming RREF=default) +static float getTMC2240_IFS(uint8_t currentRange) +{ + switch (currentRange & 0x03) { + case 0: return 1.0f; // CURRENT_RANGE=0: ~1.0A + case 1: return 2.0f; // CURRENT_RANGE=1: ~2.0A + default: return 3.0f; // CURRENT_RANGE=2/3: ~3.0A + } +} + +// Calculate TMC2240 current scale (IRUN/IHOLD value 0-31) +// TMC2240 formula (datasheet section 3): +// I_RMS = (CS_ACTUAL + 1) / 32 × (GLOBALSCALER / 256) × I_FS +// simplified (GLOBALSCALER=0, i.e. 256): +// IRUN = round(I_peak / I_FS × 32) - 1 +// note: currentMA is peak current (mA), I_FS is determined by CURRENT_RANGE +static uint8_t calculateCurrentScale_TMC2240(float currentMA, uint8_t currentRange, uint8_t globalScaler) +{ + float ifs = getTMC2240_IFS(currentRange); + float gs = (globalScaler == 0) ? 1.0f : (float)globalScaler / 256.0f; + + // IRUN = (I_peak / I_FS / GLOBALSCALER_ratio) × 32 - 1 + float cs = (currentMA / 1000.0f) / ifs / gs * 32.0f - 1.0f; + + if (cs < 0) cs = 0; + if (cs > 31) cs = 31; + + return (uint8_t)cs; +} + +// Forward declaration +static bool motor_initDriver_TMC2660(uint8_t icID, const MotorConfig *config); +static bool motor_initDriver_TMC2240(uint8_t icID, const MotorConfig *config); + +// ============================================================================ +// Initialization +// ============================================================================ + +void motor_initSubsystem(void) +{ + // Initialize SPI HAL + tmc_spi_init(); + + // Initialize TMC4361A cache + tmc4361A_initCache(); + + // Initialize TMC2660 cache + tmc2660_initCache(); + + // Initialize TMC2240 cache + tmc2240_initCache(); + + // Clear motor parameters + for (int i = 0; i < MOTOR_IC_COUNT; i++) { + motorParams[i].initialized = false; + } +} + +bool motor_init(uint8_t icID, const AxisMotionConfig *config) +{ + if (icID >= MOTOR_IC_COUNT || config == NULL) + return false; + + // save the driver type early; motor_initMotionController needs it to choose SPI_OUT_CONF + motorParams[icID].driverType = config->motor.driverType; + + // Initialize motion controller (TMC4361A) + if (!motor_initMotionController(icID, &config->motion)) + return false; + + // Initialize motor driver (TMC2660 or TMC2240) + if (!motor_initDriver(icID, &config->motor)) + return false; + + // Configure limit switches + motor_configLimitSwitches(icID, &config->limits); + + return true; +} + +// ============================================================================ +// driver chip auto-detection +// ============================================================================ + +uint8_t motor_detectDriverType(uint8_t icID) +{ + // use the TMC2660 format (format=0x0A, 20-bit auto SPI) together with a manual 40-bit Cover + // the 20-bit auto SPI does not overwrite the full 40-bit Cover response, solving the format=0x0D interference issue + // COVER_DATA_LENGTH=40 ensures the Cover transfer uses 40-bit + // SPI timing: block=4, high=4, low=4 + uint32_t spiOutConf_detect = 0x4445000A; // CDL=40 + format=0x0A + tmc4361A_writeRegister(icID, TMC4361A_SPI_OUT_CONF, spiOutConf_detect); + delayMicroseconds(500); + + // read TMC2240 IOIN via the 40-bit Cover (address 0x04) + // the TMC2240 VERSION field is at IOIN[31:24] = 0x40 + // a TMC2660 receiving 40-bit returns a 20-bit response + padding, VERSION != 0x40 + int32_t ioin = tmc2240_readRegister(icID, TMC2240_IOIN); + uint8_t version = (ioin >> 24) & 0xFF; + + DEBUG_PRINT("IC"); + DEBUG_PRINT(icID); + DEBUG_PRINT(":detect IOIN=0x"); + DEBUG_PRINTF(ioin, HEX); + DEBUG_PRINT(" ver=0x"); + DEBUG_PRINTF(version, HEX); + + if (version == 0x40) { + DEBUG_PRINTLN(" -> TMC2240"); + return DRIVER_TMC2240; + } else { + DEBUG_PRINTLN(" -> TMC2660"); + return DRIVER_TMC2660; + } +} + +bool motor_initMotionController(uint8_t icID, const MotionConfig *config) +{ + if (icID >= MOTOR_IC_COUNT || config == NULL) + return false; + + // Store motion parameters for unit conversion + motorParams[icID].clockFrequency = config->clockFrequency; + motorParams[icID].screwPitchMM = config->screwPitchMM; + motorParams[icID].fullStepsPerRev = config->fullStepsPerRev; + motorParams[icID].microsteps = config->microsteps; + motorParams[icID].stepsPerMM = (float)(config->fullStepsPerRev * config->microsteps) / config->screwPitchMM; + motorParams[icID].initialized = true; + motorParams[icID].velocity_mode = false; // consistent with the old API tmc4361A_init + + // Reset TMC4361A (same as old API) + tmc4361A_writeRegister(icID, TMC4361A_SW_RESET, 0x52535400); + + // Read VERSION_NO to verify communication + int32_t version = tmc4361A_readRegister(icID, TMC4361A_VERSION_NO); + if (version == 0 || version == -1) { + return false; // Communication failed + } + + // set CLK_FREQ first; the Cover SPI clock depends on this configuration + tmc4361A_writeRegister(icID, TMC4361A_CLK_FREQ, config->clockFrequency); + + // auto-detect the driver chip type + if (motorParams[icID].driverType == DRIVER_AUTO) { + motorParams[icID].driverType = motor_detectDriverType(icID); + } + + // Configure GENERAL_CONF + uint32_t generalConf = 0x00000000; + if (config->astartMM > 0) { + generalConf |= TMC4361A_USE_ASTART_AND_VSTART_MASK; // enable ASTART/DFINAL + } + // under TMC2240 direct_mode, the direction is determined by the TMC4361A microstep-table phase sequence, + // the TMC2240 SHAFT bit is ineffective (it only affects STEP/DIR mode). + // invert the TMC4361A internal microstep-table direction to compensate for the phase-mapping difference between format 0x0D and 0x0A + if (motorParams[icID].driverType == DRIVER_TMC2240) { + generalConf |= TMC4361A_REVERSE_MOTOR_DIR_MASK; // bit 28 + } + tmc4361A_writeRegister(icID, TMC4361A_GENERAL_CONF, generalConf); + + // Configure SPI_OUT_CONF - choose the SPI output format based on the driver chip type + uint32_t spiOutConf; + if (motorParams[icID].driverType == DRIVER_TMC2240) { + // TMC2240: SPI_OUTPUT_FORMAT=0x0D (TMC2130/TMC2240 SPI current-transfer mode, 40-bit) + // equivalent to the TMC2660 SDOFF mode: the TMC4361A directly controls the coil current + // SPI timing: block=4, high=4, low=4 + // COVER_DATA_LENGTH=40 (bits[19:13]), explicitly specifying a 40-bit Cover length + spiOutConf = 0x4445000D; + } else { + // TMC2660: SPI_OUTPUT_FORMAT=0x0A (TMC26x 20-bit SPI mode) + // 0x4440108A: SCALE_VAL_TRANSFER_EN=1, COVER_DATA_LENGTH for 20-bit + spiOutConf = 0x4440108A; + } + tmc4361A_writeRegister(icID, TMC4361A_SPI_OUT_CONF, spiOutConf); + + // CLK_FREQ was already set before detection + + // Configure ramp mode + // RAMPMODE: 0=hold, 1=trapezoid, 2=S-shaped, 4=position mode + uint32_t rampMode = config->useSShapedRamp ? 6 : 5; // Position mode + ramp type + tmc4361A_writeRegister(icID, TMC4361A_RAMPMODE, rampMode); + + // ======================================================================== + // cache the ramp parameters (consistent with the old API rampParam[]) + // ======================================================================== + + // compute and cache the velocity/acceleration parameters + int32_t vmax = motor_velocityMMToInternal(icID, config->maxVelocityMM); + uint32_t amax = motor_accelMMToInternal(icID, config->maxAccelerationMM); + float decelMM = config->maxDecelerationMM > 0 ? config->maxDecelerationMM : config->maxAccelerationMM; + uint32_t dmax = motor_accelMMToInternal(icID, decelMM); + + motorParams[icID].vmax = vmax; + motorParams[icID].amax = amax; + motorParams[icID].dmax = dmax; + // ASTART / DFINAL: start acceleration and final deceleration + uint32_t astart = config->astartMM > 0 ? motor_accelMMToInternal(icID, config->astartMM) : 0; + float dfinalMM = config->dfinalMM > 0 ? config->dfinalMM : config->astartMM; + uint32_t dfinal = dfinalMM > 0 ? motor_accelMMToInternal(icID, dfinalMM) : 0; + motorParams[icID].astart = astart; + motorParams[icID].dfinal = dfinal; + + // write to the hardware registers + tmc4361A_writeRegister(icID, TMC4361A_VMAX, vmax); + tmc4361A_writeRegister(icID, TMC4361A_AMAX, amax); + tmc4361A_writeRegister(icID, TMC4361A_DMAX, dmax); + + // Configure S-shaped ramp if enabled + if (config->useSShapedRamp) { + // if the BOW parameters are 0, compute them automatically (consistent with the old API tmc4361A_adjustBows) + if (config->bow1 == 0 && config->bow2 == 0 && config->bow3 == 0 && config->bow4 == 0) { + motor_adjustBows(icID); + } else { + motorParams[icID].bow1 = config->bow1; + motorParams[icID].bow2 = config->bow2; + motorParams[icID].bow3 = config->bow3; + motorParams[icID].bow4 = config->bow4; + } + + // write the BOW registers + tmc4361A_writeRegister(icID, TMC4361A_BOW1, motorParams[icID].bow1); + tmc4361A_writeRegister(icID, TMC4361A_BOW2, motorParams[icID].bow2); + tmc4361A_writeRegister(icID, TMC4361A_BOW3, motorParams[icID].bow3); + tmc4361A_writeRegister(icID, TMC4361A_BOW4, motorParams[icID].bow4); + } else { + motorParams[icID].bow1 = 0; + motorParams[icID].bow2 = 0; + motorParams[icID].bow3 = 0; + motorParams[icID].bow4 = 0; + } + + // Set VSTART, VSTOP, ASTART, DFINAL + tmc4361A_writeRegister(icID, TMC4361A_VSTART, 0); + tmc4361A_writeRegister(icID, TMC4361A_VSTOP, 0); + tmc4361A_writeRegister(icID, TMC4361A_ASTART, motorParams[icID].astart); + tmc4361A_writeRegister(icID, TMC4361A_DFINAL, motorParams[icID].dfinal); + + // ======================================================================== + // key configuration: microstepping and steps per revolution (consistent with the old API tmc4361A_writeMicrosteps/writeSPR) + // ======================================================================== + + // compute the MSTEP_PER_FS value: 256->0, 128->1, ..., 1->8 + uint16_t mstep = config->microsteps; + uint8_t mstepVal = 0; + if (mstep > 0 && (mstep & (mstep - 1)) == 0 && mstep <= 256) { + // compute log2(mstep) + 1, then 9 - result + uint8_t bitsSet = 0; + while (mstep > 0) { + bitsSet++; + mstep >>= 1; + } + mstepVal = 9 - bitsSet; + } + + // combine STEP_CONF: MSTEP_PER_FS (bit 0-3) + FS_PER_REV (bit 4-15) + uint32_t stepConf = (mstepVal & TMC4361A_MSTEP_PER_FS_MASK) | + ((uint32_t)config->fullStepsPerRev << TMC4361A_FS_PER_REV_SHIFT); + tmc4361A_writeRegister(icID, TMC4361A_STEP_CONF, stepConf); + + // ======================================================================== + // key configuration: current scaling + // the TMC4361A internally uses SCALE_VALUES to compute the coil current amplitude, applicable to all SPI output formats: + // - TMC2660 (format 0x0A): the 20-bit SPI data contains the scaled current value + // - TMC2240 (format 0x0D): 40-bit SPI writes the DIRECT_MODE register, which also needs scaling + // not configuring SCALE_VALUES would send zero current -> the motor does not move + // ======================================================================== + + // SCALE_VALUES + CURRENT_CONF (consistent with the old API tmc4361A_cScaleInit) + uint32_t scaleValues = (128 << TMC4361A_HOLD_SCALE_VAL_SHIFT) | // hold = 50% + (255 << TMC4361A_DRV2_SCALE_VAL_SHIFT) | // drv2 = 100% + (255 << TMC4361A_DRV1_SCALE_VAL_SHIFT) | // drv1 = 100% + (255 << TMC4361A_BOOST_SCALE_VAL_SHIFT); // boost = 100% + tmc4361A_writeRegister(icID, TMC4361A_SCALE_VALUES, scaleValues); + + uint32_t currentConf = tmc4361A_readRegister(icID, TMC4361A_CURRENT_CONF); + currentConf |= TMC4361A_DRIVE_CURRENT_SCALE_EN_MASK; // bit 1 + currentConf |= TMC4361A_HOLD_CURRENT_SCALE_EN_MASK; // bit 0 + currentConf |= TMC4361A_BOOST_CURRENT_ON_ACC_EN_MASK; // bit 2 + currentConf |= TMC4361A_BOOST_CURRENT_AFTER_START_EN_MASK; // bit 4 + tmc4361A_writeRegister(icID, TMC4361A_CURRENT_CONF, currentConf); + + return true; +} + +bool motor_initDriver(uint8_t icID, const MotorConfig *config) +{ + if (icID >= MOTOR_IC_COUNT || config == NULL) + return false; + + // cache the driver type and rSense + motorParams[icID].driverType = config->driverType; + motorParams[icID].rSense = config->rSense; + + if (config->driverType == DRIVER_TMC2240) { + return motor_initDriver_TMC2240(icID, config); + } else { + return motor_initDriver_TMC2660(icID, config); + } +} + +// ======================================================================== +// TMC2660 driver initialization +// ======================================================================== +static bool motor_initDriver_TMC2660(uint8_t icID, const MotorConfig *config) +{ + // cache toff for enableDriver to restore + motorParams[icID].toff = config->toff; + + // Calculate current scale + uint8_t cs = calculateCurrentScale(config->runCurrentMA, config->rSense); + + // TMC2660 initialization - same order as the old API + // old API order: CHOPCONF -> SMARTEN -> SGCSCONF -> DRVCONF + // note: in SPI mode (SDOFF=1), DRVCTRL is not sent + + // 1. Configure CHOPCONF (old API: 0x000900C3) + uint8_t hend_reg = (uint8_t)(config->hend + 3); // Offset by 3 + uint32_t chopconf = TMC2660_SET_TBL(config->tbl) | + TMC2660_SET_HEND(hend_reg) | + TMC2660_SET_HSTRT(config->hstrt) | + TMC2660_SET_TOFF(config->toff); + tmc2660_writeRegister(icID, TMC2660_CHOPCONF, chopconf); + + // 2. Configure SMARTEN (old API: 0x000A0000, CoolStep disabled) + tmc2660_writeRegister(icID, TMC2660_SMARTEN, 0); + + // 3. Configure SGCSCONF (old API: 0x000C000A) + uint8_t sgt = (uint8_t)(config->stallThreshold & 0x7F); + uint32_t sgcsconf = TMC2660_SET_CS(cs) | + TMC2660_SET_SGT(sgt) | + TMC2660_SET_SFILT(config->stallFilter ? 1 : 0); + tmc2660_writeRegister(icID, TMC2660_SGCSCONF, sgcsconf); + + // 4. Configure DRVCONF (old API: 0x000E00A1) + // SDOFF=1: SPI mode (motion control via SPI, not Step/Dir) + // VSENSE=0: High sense resistor voltage range (V_fs=0.310V) + // RDSEL=2: StallGuard2 value and CoolStep current level in response + uint32_t drvconf = TMC2660_SET_RDSEL(2) | TMC2660_SET_VSENSE(0) | TMC2660_SET_SDOFF(1) | 0x01; + tmc2660_writeRegister(icID, TMC2660_DRVCONF, drvconf); + + // note: in SPI mode (SDOFF=1), DRVCTRL is not sent + // microstepping is controlled by the TMC4361A STEP_CONF register + + return true; +} + +// ======================================================================== +// TMC2240 driver initialization +// ======================================================================== +static bool motor_initDriver_TMC2240(uint8_t icID, const MotorConfig *config) +{ + // cache the TMC2240-specific parameters (needed at runtime by setRunCurrent / enableDriver) + motorParams[icID].currentRange = config->currentRange; + motorParams[icID].toff = config->toff; + + // note: SPI_OUTPUT_FORMAT=0x0D stays active and must not be disabled (format=0 turns off the SPI output hardware) + // Cover writes and the automatic SPI output are serialized by the TMC4361A hardware, so writes should be reliable + // Cover reads may be disturbed by the automatic SPI (read-back values are unreliable), but this does not affect configuration writes + + // 1. DRV_CONF - set CURRENT_RANGE and SLOPE_CONTROL + // CURRENT_RANGE: 0=1A, 1=2A, 2=3A, 3=3A + // SLOPE_CONTROL: 1 = 200V/us (default) + uint32_t drvConf = ((uint32_t)(config->currentRange & 0x03) << 0) | + (1 << 4); // SLOPE_CONTROL=1 + tmc2240_writeRegister(icID, TMC2240_DRV_CONF, drvConf); + + // 2. GLOBAL_SCALER (0=256, i.e. full scale; 32-255 scaling) + tmc2240_writeRegister(icID, TMC2240_GLOBAL_SCALER, + config->globalScaler == 0 ? 0 : config->globalScaler); + + // 3. compute the current -- I_FS based on CURRENT_RANGE + uint8_t irun = calculateCurrentScale_TMC2240(config->runCurrentMA, + config->currentRange, + config->globalScaler); + uint8_t ihold = (uint8_t)(irun * config->holdCurrentRatio); + if (ihold > 31) ihold = 31; + + // 4. IHOLD_IRUN - current configuration + uint32_t iholdIrun = ((uint32_t)ihold << TMC2240_IHOLD_SHIFT) | + ((uint32_t)irun << TMC2240_IRUN_SHIFT) | + ((uint32_t)(config->iholdDelay & 0x0F) << TMC2240_IHOLDDELAY_SHIFT); + tmc2240_writeRegister(icID, TMC2240_IHOLD_IRUN, iholdIrun); + + // 5. TPOWERDOWN - hold-current delay (default 10) + tmc2240_writeRegister(icID, TMC2240_TPOWERDOWN, 10); + + // 6. GCONF - global configuration + uint32_t gconf = 0x00000000; + // direct_mode (bit 16): the TMC4361A directly controls the coil current via SPI (DIRECT_MODE register) + // must be enabled, otherwise the TMC2240 waits for Step/Dir signals and does not respond to SPI current commands + gconf |= TMC2240_DIRECT_MODE_MASK; // bit 16: direct coil current control via SPI + // note: SHAFT (bit 4) is ineffective in direct_mode; the direction is controlled by the TMC4361A REVERSE_MOTOR_DIR + if (config->enableStealthChop) { + gconf |= TMC2240_EN_PWM_MODE_MASK; // bit 2: StealthChop enable + } + tmc2240_writeRegister(icID, TMC2240_GCONF, gconf); + + // 7. CHOPCONF - Chopper configuration (includes the MRES microstepping setting) + // MRES encoding: 0=256, 1=128, 2=64, ..., 8=full step (consistent with TMC4361A STEP_CONF) + uint8_t mresVal = config->microstepRes; // passed in by Axis::begin(), usually 0 (256 microsteps) + + uint32_t chopconf = ((uint32_t)(config->toff & 0x0F) << TMC2240_TOFF_SHIFT) | + ((uint32_t)(config->hstrt & 0x07) << TMC2240_HSTRT_TFD210_SHIFT) | + ((uint32_t)((config->hend + 3) & 0x0F) << TMC2240_HEND_OFFSET_SHIFT) | + ((uint32_t)(config->tbl & 0x03) << TMC2240_TBL_SHIFT) | + ((uint32_t)(mresVal & 0x0F) << TMC2240_MRES_SHIFT) | + (config->interpolation ? (1 << TMC2240_INTPOL_SHIFT) : 0); + tmc2240_writeRegister(icID, TMC2240_CHOPCONF, chopconf); + + // 8. PWMCONF - StealthChop PWM configuration + if (config->enableStealthChop) { + // defaults: pwm_autoscale=1, pwm_autograd=1 + tmc2240_writeRegister(icID, TMC2240_PWMCONF, 0xC44C001E); + } + + // clear the GSTAT reset flag + tmc2240_writeRegister(icID, TMC2240_GSTAT, 0x07); + // ---- END DEBUG ---- + + return true; +} + +void motor_configLimitSwitches(uint8_t icID, const LimitConfig *config) +{ + if (icID >= MOTOR_IC_COUNT || config == NULL) + return; + + // Read current REFERENCE_CONF to preserve other bits (consistent with the old API setBits behavior) + uint32_t refConf = tmc4361A_readRegister(icID, TMC4361A_REFERENCE_CONF); + + // Left switch configuration + // 2026-06-06: the hard-stop enable (STOP_LEFT_EN) is decoupled from "polarity / position latch". + // the polarity (POL_STOP_LEFT) and latch (LATCH_X_ON_ACTIVE_L) are always written per the config, independent of enable -- + // so after disabling the chip hard stop (enableLeft=false, using software stop), STATUS STOPL_ACTIVE_F still reflects the + // switch level correctly per polarity (software poll needs it), and the X_LATCH used for homing retract-to-safe still works. + // the old implementation gated both inside if(enableLeft) -> when enable=false the polarity was lost (read inverted) and the latch failed. + // behavior for enable=true axes is completely unchanged (no regression). + if (config->enableLeft) + refConf |= TMC4361A_STOP_LEFT_EN_MASK; // bit 0 + else + refConf &= ~TMC4361A_STOP_LEFT_EN_MASK; + if (config->leftPolarity) + refConf |= TMC4361A_POL_STOP_LEFT_MASK; // bit 2 + else + refConf &= ~TMC4361A_POL_STOP_LEFT_MASK; + refConf |= TMC4361A_LATCH_X_ON_ACTIVE_L_MASK; // bit 11 + + // Right switch configuration (same as above: decoupled) + if (config->enableRight) + refConf |= TMC4361A_STOP_RIGHT_EN_MASK; // bit 1 + else + refConf &= ~TMC4361A_STOP_RIGHT_EN_MASK; + if (config->rightPolarity) + refConf |= TMC4361A_POL_STOP_RIGHT_MASK; // bit 3 + else + refConf &= ~TMC4361A_POL_STOP_RIGHT_MASK; + refConf |= TMC4361A_LATCH_X_ON_ACTIVE_R_MASK; // bit 13 + + // Invert stop direction: swap the logical meaning of the left/right limit switches + // consistent with the master-branch old API (tmc4361A_enableLimitSwitch): + // if (flipped != 0) setBits(INVERT_STOP_DIRECTION_MASK) + if (config->leftFlipped || config->rightFlipped) { + refConf |= TMC4361A_INVERT_STOP_DIRECTION_MASK; // bit 4 + } else { + refConf &= ~TMC4361A_INVERT_STOP_DIRECTION_MASK; + } + + // note: do not set SOFT_STOP_EN (bit 5) + // SOFT_STOP_EN=1 makes the chip enter an internal soft-stop state machine when a limit triggers, + // locking RAMPMODE/VMAX/XTARGET writes and causing homing stop to fail. + // kept consistent with the master-branch old API (tmc4361A_enableLimitSwitch): hard stop. + + tmc4361A_writeRegister(icID, TMC4361A_REFERENCE_CONF, refConf); +} + +void motor_setHardwareStopEnable(uint8_t icID, uint8_t side, bool enable) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + uint32_t refConf = tmc4361A_readRegister(icID, TMC4361A_REFERENCE_CONF); + + uint32_t mask = 0; + if (side & 0x01) // LEFT_SW + mask |= TMC4361A_STOP_LEFT_EN_MASK; + if (side & 0x02) // RGHT_SW + mask |= TMC4361A_STOP_RIGHT_EN_MASK; + + if (enable) { + refConf |= mask; + } else { + refConf &= ~mask; + } + + tmc4361A_writeRegister(icID, TMC4361A_REFERENCE_CONF, refConf); +} + +// ============================================================================ +// Motion Control +// ============================================================================ + +void motor_moveToPosition(uint8_t icID, float positionMM) +{ + int32_t microsteps = motor_mmToMicrosteps(icID, positionMM); + motor_moveToMicrosteps(icID, microsteps); +} + +void motor_moveByDistance(uint8_t icID, float distanceMM) +{ + int32_t current = motor_getPositionMicrosteps(icID); + int32_t delta = motor_mmToMicrosteps(icID, distanceMM); + motor_moveToMicrosteps(icID, current + delta); +} + +bool motor_moveToMicrosteps(uint8_t icID, int32_t position) +{ + if (icID >= MOTOR_IC_COUNT) + return false; + + // ======================================================================== + // an implementation exactly matching the old API tmc4361A_moveTo + // ======================================================================== + + // state restore: call sRampInit only when velocity_mode == true + // as in the old API: if(tmc4361A->velocity_mode) { tmc4361A_sRampInit(); velocity_mode = false; } + if (motorParams[icID].velocity_mode) { + // ==================================================================== + // sRampInit-equivalent implementation (exactly the same as the old API tmc4361A_sRampInit) + // ==================================================================== + + // 1. RAMPMODE: use setBits to set position mode + S-shaped ramp + // old API: tmc4361A_setBits(tmc4361A, TMC4361A_RAMPMODE, TMC4361A_RAMP_POSITION | TMC4361A_RAMP_SSHAPE); + uint32_t rampMode = tmc4361A_readRegister(icID, TMC4361A_RAMPMODE); + rampMode |= (TMC4361A_RAMP_POSITION | TMC4361A_RAMP_SSHAPE); + tmc4361A_writeRegister(icID, TMC4361A_RAMPMODE, rampMode); + + // 2. restore the USE_ASTART_AND_VSTART setting (decided by the astart config) + uint32_t generalConf = tmc4361A_readRegister(icID, TMC4361A_GENERAL_CONF); + if (motorParams[icID].astart > 0) { + generalConf |= TMC4361A_USE_ASTART_AND_VSTART_MASK; + } else { + generalConf &= ~TMC4361A_USE_ASTART_AND_VSTART_MASK; + } + tmc4361A_writeRegister(icID, TMC4361A_GENERAL_CONF, generalConf); + + // 3. rewrite all ramp parameters (consistent with the old API sRampInit) + tmc4361A_writeRegister(icID, TMC4361A_BOW1, motorParams[icID].bow1); + tmc4361A_writeRegister(icID, TMC4361A_BOW2, motorParams[icID].bow2); + tmc4361A_writeRegister(icID, TMC4361A_BOW3, motorParams[icID].bow3); + tmc4361A_writeRegister(icID, TMC4361A_BOW4, motorParams[icID].bow4); + tmc4361A_writeRegister(icID, TMC4361A_AMAX, motorParams[icID].amax); + tmc4361A_writeRegister(icID, TMC4361A_DMAX, motorParams[icID].dmax); + tmc4361A_writeRegister(icID, TMC4361A_ASTART, motorParams[icID].astart); + tmc4361A_writeRegister(icID, TMC4361A_DFINAL, motorParams[icID].dfinal); + tmc4361A_writeRegister(icID, TMC4361A_VMAX, motorParams[icID].vmax); + + // 4. clear the velocity_mode flag + motorParams[icID].velocity_mode = false; + + } + + // unconditionally write back VMAX (consistent with the old API tmc4361A_moveTo) + // the old API writes VMAX on every moveTo, ensuring that even if it is zeroed by something external (e.g. motor_stop) + // the correct speed is restored + tmc4361A_writeRegister(icID, TMC4361A_VMAX, motorParams[icID].vmax); + + // ======================================================================== + // write the target position (consistent with the old API tmc4361A_moveTo) + // ======================================================================== + + // virtual-limit recovery (TMC4361A Programming Guide section 10.4): + // "precondition: the stop switch is no longer active OR the stop switch is disabled. Then clear the events." + // + // strategy: disable the activated virtual_limit_en -> clear events -> write XTARGET. + // do not restore the enable bit here: it can only be restored after the motor leaves the boundary (managed by the Axis layer). + // if restored immediately, XACTUAL is still at the boundary -> VSTOP re-triggers immediately -> the motor cannot move. + uint32_t status = tmc4361A_readRegister(icID, TMC4361A_STATUS); + bool vstopL = status & TMC4361A_VSTOPL_ACTIVE_F_MASK; + bool vstopR = status & TMC4361A_VSTOPR_ACTIVE_F_MASK; + bool vstopWasActive = vstopL || vstopR; + + if (vstopL || vstopR) { + uint32_t refConf = tmc4361A_readRegister(icID, TMC4361A_REFERENCE_CONF); + + // disable the activated virtual limit (to satisfy the recovery precondition) + if (vstopL) + refConf &= ~TMC4361A_VIRTUAL_LEFT_LIMIT_EN_MASK; + if (vstopR) + refConf &= ~TMC4361A_VIRTUAL_RIGHT_LIMIT_EN_MASK; + + tmc4361A_writeRegister(icID, TMC4361A_REFERENCE_CONF, refConf); + tmc4361A_readRegister(icID, TMC4361A_EVENTS); // clear events (recovery action) + + tmc4361A_writeRegister(icID, TMC4361A_XTARGET, position); + tmc4361A_readRegister(icID, TMC4361A_EVENTS); // clear any new events + } else { + // normal path (no virtual limit active) + tmc4361A_readRegister(icID, TMC4361A_EVENTS); + tmc4361A_writeRegister(icID, TMC4361A_XTARGET, position); + tmc4361A_readRegister(icID, TMC4361A_EVENTS); + } + + // Read X_ACTUAL to get it to refresh + tmc4361A_readRegister(icID, TMC4361A_XACTUAL); + + // Return vstop state so axis layer can skip its own STATUS read + // (saves ~10-20µs SPI per move; 2026-05-18 acquisition optimization #2.2) + return vstopWasActive; +} + +void motor_rotateVelocity(uint8_t icID, float velocityMM) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Switch to velocity mode + uint32_t rampMode = tmc4361A_readRegister(icID, TMC4361A_RAMPMODE); + rampMode &= ~TMC4361A_RAMP_POSITION; // Clear position mode bit + tmc4361A_writeRegister(icID, TMC4361A_RAMPMODE, rampMode); + + // Set velocity + int32_t vel = motor_velocityMMToInternal(icID, velocityMM); + tmc4361A_writeRegister(icID, TMC4361A_VMAX, vel >= 0 ? vel : -vel); + + // Direction is determined by sign of VMAX in velocity mode + if (vel < 0) { + // For negative velocity, we need to handle direction + // This depends on the specific implementation + } +} + +void motor_stop(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Set VMAX to 0 for smooth stop + tmc4361A_writeRegister(icID, TMC4361A_VMAX, 0); +} + +void motor_emergencyStop(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Set target to current position for immediate stop + int32_t current = tmc4361A_readRegister(icID, TMC4361A_XACTUAL); + tmc4361A_writeRegister(icID, TMC4361A_XTARGET, current); + tmc4361A_writeRegister(icID, TMC4361A_VMAX, 0); +} + +// ============================================================================ +// Status Query +// ============================================================================ + +bool motor_isTargetReached(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return true; + + // equivalent to legacy Squid tmc4361A_isRunning (negated): target reached AND velocity zero AND ramp not changing + // - TARGET_REACHED_F (bit 0): XACTUAL == XTARGET + // - VEL_STATE_F (bits 3-4): 00 = velocity has reached zero (non-zero = +/- velocity) + // - RAMP_STATE_F (bits 5-6): 00 = ramp idle (non-zero = acc/dec/const) + // reads multiple bits in a single STATUS read at no extra SPI cost; prevents the edge case at the end of the chip ramp where "XACTUAL briefly == XTARGET + // but the speed has not reached zero" is misjudged + uint32_t status = tmc4361A_readRegister(icID, TMC4361A_STATUS); + return (status & TMC4361A_TARGET_REACHED_F_MASK) && + !(status & (TMC4361A_VEL_STATE_F_MASK | TMC4361A_RAMP_STATE_F_MASK)); +} + +bool motor_isRunning(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return false; + + int32_t velocity = tmc4361A_readRegister(icID, TMC4361A_VACTUAL); + return velocity != 0; +} + +float motor_getPositionMM(uint8_t icID) +{ + int32_t microsteps = motor_getPositionMicrosteps(icID); + return motor_microstepsToMM(icID, microsteps); +} + +int32_t motor_getPositionMicrosteps(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return 0; + + return tmc4361A_readRegister(icID, TMC4361A_XACTUAL); +} + +int32_t motor_getTargetMicrosteps(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return 0; + + return tmc4361A_readRegister(icID, TMC4361A_XTARGET); +} + +float motor_getVelocityMM(uint8_t icID) +{ + int32_t velInternal = motor_getVelocityInternal(icID); + return motor_velocityInternalToMM(icID, velInternal); +} + +int32_t motor_getVelocityInternal(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return 0; + + return tmc4361A_readRegister(icID, TMC4361A_VACTUAL); +} + +uint8_t motor_readLimitSwitches(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return 0; + + uint32_t status = tmc4361A_readRegister(icID, TMC4361A_STATUS); + + // STOPL_ACTIVE_F is bit 7 (0x80), STOPR_ACTIVE_F is bit 8 (0x100) + // Mask and shift to get bits 0 and 1 + status &= (TMC4361A_STOPL_ACTIVE_F_MASK | TMC4361A_STOPR_ACTIVE_F_MASK); + status >>= TMC4361A_STOPL_ACTIVE_F_SHIFT; + + return (uint8_t)(status & 0x03); +} + +uint32_t motor_readStatus(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return 0; + + return tmc4361A_readRegister(icID, TMC4361A_STATUS); +} + +uint32_t motor_readEvents(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return 0; + + return tmc4361A_readRegister(icID, TMC4361A_EVENTS); +} + +// ============================================================================ +// Parameter Setting +// ============================================================================ + +void motor_setMaxVelocity(uint8_t icID, float velocityMM) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + int32_t vel = motor_velocityMMToInternal(icID, velocityMM); + motorParams[icID].vmax = vel; // saved for position-mode restore (consistent with the old API rampParam[VMAX_IDX]) + + // consistent with the old API tmc4361A_setMaxSpeed: automatically recompute the BOW parameters + motor_adjustBows(icID); + + // write to hardware (sRampInit-equivalent) + tmc4361A_writeRegister(icID, TMC4361A_VMAX, motorParams[icID].vmax); + tmc4361A_writeRegister(icID, TMC4361A_BOW1, motorParams[icID].bow1); + tmc4361A_writeRegister(icID, TMC4361A_BOW2, motorParams[icID].bow2); + tmc4361A_writeRegister(icID, TMC4361A_BOW3, motorParams[icID].bow3); + tmc4361A_writeRegister(icID, TMC4361A_BOW4, motorParams[icID].bow4); +} + +void motor_resetRampMode(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // read the current RAMPMODE + [[maybe_unused]] uint32_t rampModeBefore = tmc4361A_readRegister(icID, TMC4361A_RAMPMODE); + + // reset RAMPMODE to position mode + S-shaped ramp (consistent with initialization) + // this must be called after a RESET command or a hardware-limit trigger + uint32_t rampMode = 0x06; // S-shaped position mode + tmc4361A_writeRegister(icID, TMC4361A_RAMPMODE, rampMode); + + // read the RAMPMODE after setting + [[maybe_unused]] uint32_t rampModeAfter = tmc4361A_readRegister(icID, TMC4361A_RAMPMODE); + + // debug output + DEBUG_PRINT("motor_resetRampMode: icID="); + DEBUG_PRINT(icID); + DEBUG_PRINT(" RAMPMODE: 0x"); + DEBUG_PRINTF(rampModeBefore, HEX); + DEBUG_PRINT(" -> 0x"); + DEBUG_PRINTLNF(rampModeAfter, HEX); +} + +void motor_setMaxAcceleration(uint8_t icID, float accelerationMM) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + uint32_t accel = motor_accelMMToInternal(icID, accelerationMM); + motorParams[icID].amax = accel; // cache (consistent with the old API rampParam[AMAX_IDX]) + motorParams[icID].dmax = accel; // consistent with the old API: DMAX = AMAX + + // consistent with the old API tmc4361A_setMaxAcceleration: automatically recompute the BOW parameters + motor_adjustBows(icID); + + // write to hardware (sRampInit-equivalent) + tmc4361A_writeRegister(icID, TMC4361A_AMAX, motorParams[icID].amax); + tmc4361A_writeRegister(icID, TMC4361A_DMAX, motorParams[icID].dmax); + tmc4361A_writeRegister(icID, TMC4361A_BOW1, motorParams[icID].bow1); + tmc4361A_writeRegister(icID, TMC4361A_BOW2, motorParams[icID].bow2); + tmc4361A_writeRegister(icID, TMC4361A_BOW3, motorParams[icID].bow3); + tmc4361A_writeRegister(icID, TMC4361A_BOW4, motorParams[icID].bow4); +} + +void motor_setMaxDeceleration(uint8_t icID, float decelerationMM) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + uint32_t decel = motor_accelMMToInternal(icID, decelerationMM); + motorParams[icID].dmax = decel; // cache (consistent with the old API rampParam[DMAX_IDX]) + tmc4361A_writeRegister(icID, TMC4361A_DMAX, decel); +} + +void motor_setCurrentPosition(uint8_t icID, float positionMM) +{ + int32_t microsteps = motor_mmToMicrosteps(icID, positionMM); + motor_setCurrentPositionMicrosteps(icID, microsteps); +} + +void motor_setCurrentPositionMicrosteps(uint8_t icID, int32_t position) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // consistent with the old API tmc4361A_setCurrentPosition behavior: + // 1. stop the motor first (set VMAX=0) + // 2. set XACTUAL and XTARGET + // 3. set velocity_mode=true; VMAX will be restored on the next moveToMicrosteps + tmc4361A_writeRegister(icID, TMC4361A_VMAX, 0); + tmc4361A_writeRegister(icID, TMC4361A_XACTUAL, position); + tmc4361A_writeRegister(icID, TMC4361A_XTARGET, position); + tmc4361A_writeRegister(icID, TMC4361A_ENC_POS, position); // sync the encoder position + motorParams[icID].velocity_mode = true; +} + +void motor_setMicrosteps(uint8_t icID, uint16_t microsteps) +{ + if (icID >= MOTOR_IC_COUNT || !motorParams[icID].initialized) + return; + + // update the cache + motorParams[icID].microsteps = microsteps; + motorParams[icID].stepsPerMM = (float)(motorParams[icID].fullStepsPerRev * microsteps) / motorParams[icID].screwPitchMM; + + // compute the MSTEP_PER_FS value: 256->0, 128->1, ..., 1->8 + uint16_t mstep = microsteps; + uint8_t mstepVal = 0; + if (mstep > 0 && (mstep & (mstep - 1)) == 0 && mstep <= 256) { + uint8_t bitsSet = 0; + while (mstep > 0) { + bitsSet++; + mstep >>= 1; + } + mstepVal = 9 - bitsSet; + } + + // combine STEP_CONF: MSTEP_PER_FS (bit 0-3) + FS_PER_REV (bit 4-15) + uint32_t stepConf = (mstepVal & TMC4361A_MSTEP_PER_FS_MASK) | + ((uint32_t)motorParams[icID].fullStepsPerRev << TMC4361A_FS_PER_REV_SHIFT); + tmc4361A_writeRegister(icID, TMC4361A_STEP_CONF, stepConf); + + // TMC2240: also update CHOPCONF.MRES (the TMC2240's MRES must match the TMC4361A's STEP_CONF) + // note: cannot use tmc2240_fieldWrite (read-modify-write), because SPI_OUTPUT_FORMAT=0x0D + // the automatic SPI output disturbs Cover reads; an unreliable read-back would corrupt CHOPCONF (TOFF=0 -> driver off). + // use the shadow register to get the last-written CHOPCONF value instead. + if (motorParams[icID].driverType == DRIVER_TMC2240) { + uint32_t chopconf = (uint32_t)tmc2240_shadowRegister[icID][TMC2240_CHOPCONF]; + chopconf = (chopconf & ~((uint32_t)0x0F << TMC2240_MRES_SHIFT)) | + ((uint32_t)(mstepVal & 0x0F) << TMC2240_MRES_SHIFT); + tmc2240_writeRegister(icID, TMC2240_CHOPCONF, chopconf); + } +} + +void motor_setRunCurrent(uint8_t icID, float currentMA) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + if (motorParams[icID].driverType == DRIVER_TMC2240) { + uint8_t irun = calculateCurrentScale_TMC2240(currentMA, + motorParams[icID].currentRange, 0); + tmc2240_setRunCurrent(icID, irun); + } else { + float rSense = motorParams[icID].rSense > 0 ? motorParams[icID].rSense : 0.22f; + uint8_t cs = calculateCurrentScale(currentMA, rSense); + tmc2660_setRunCurrent(icID, cs); + } +} + +void motor_enableDriver(uint8_t icID, bool enable) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + if (motorParams[icID].driverType == DRIVER_TMC2240) { + if (enable) { + // use the cached TOFF to restore the driver (rather than a hardcoded default) + uint32_t chopconf = tmc2240_readRegister(icID, TMC2240_CHOPCONF); + uint8_t currentToff = (chopconf & TMC2240_TOFF_MASK) >> TMC2240_TOFF_SHIFT; + if (currentToff == 0) { + uint8_t toff = motorParams[icID].toff > 0 ? motorParams[icID].toff : 3; + tmc2240_fieldWrite(icID, TMC2240_TOFF_FIELD, toff); + } + } else { + tmc2240_fieldWrite(icID, TMC2240_TOFF_FIELD, 0); + } + } else { + tmc2660_enableDriver(icID, enable); + } +} + +// ============================================================================ +// Unit Conversion +// ============================================================================ + +int32_t motor_mmToMicrosteps(uint8_t icID, float mm) +{ + if (icID >= MOTOR_IC_COUNT || !motorParams[icID].initialized) + return 0; + + return (int32_t)(mm * motorParams[icID].stepsPerMM); +} + +float motor_microstepsToMM(uint8_t icID, int32_t microsteps) +{ + if (icID >= MOTOR_IC_COUNT || !motorParams[icID].initialized) + return 0.0f; + + return (float)microsteps / motorParams[icID].stepsPerMM; +} + +int32_t motor_velocityMMToInternal(uint8_t icID, float velocityMM) +{ + if (icID >= MOTOR_IC_COUNT || !motorParams[icID].initialized) + return 0; + + // TMC4361A velocity format: multiply by 2^8 (256) to account for 8 decimal places + // Formula matches old API: (1 << 8) * mm * stepsPerMM + int32_t velocity = (int32_t)((1 << 8) * velocityMM * motorParams[icID].stepsPerMM); + + return velocity; +} + +float motor_velocityInternalToMM(uint8_t icID, int32_t velocityInternal) +{ + if (icID >= MOTOR_IC_COUNT || !motorParams[icID].initialized) + return 0.0f; + + // Reverse of above + float velocityPPS = (float)velocityInternal * (float)motorParams[icID].clockFrequency / 65536.0f; + return velocityPPS / motorParams[icID].stepsPerMM; +} + +uint32_t motor_accelMMToInternal(uint8_t icID, float accelMM) +{ + if (icID >= MOTOR_IC_COUNT || !motorParams[icID].initialized) + return 0; + + // TMC4361A acceleration format: multiply by 2^2 (4) to account for 2 decimal places + // Formula matches old API: (1 << 2) * mm * stepsPerMM + uint32_t accel = (uint32_t)((1 << 2) * accelMM * motorParams[icID].stepsPerMM); + + return accel; +} + +// ============================================================================ +// Homing +// ============================================================================ + +void motor_startHoming(uint8_t icID, int8_t direction, float velocityMM) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Configure for velocity mode toward limit switch + motor_rotateVelocity(icID, direction > 0 ? velocityMM : -velocityMM); +} + +void motor_setHomePosition(uint8_t icID, float positionMM) +{ + motor_setCurrentPosition(icID, positionMM); +} + +void motor_enableHomingLimit(uint8_t icID, uint8_t polarity, uint8_t whichSwitch, + int32_t safetyMarginMicrosteps) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Read current REFERENCE_CONF + uint32_t refConf = tmc4361A_readRegister(icID, TMC4361A_REFERENCE_CONF); + + // Configure HOME_EVENT and home switch (consistent with the old API tmc4361A_enableHomingLimit) + if (whichSwitch == 0x01) { // Left switch (LEFT_SW) + if (polarity != 0) { + // Active high: HOME_REF = 0 indicates positive direction + refConf |= (0b1100 << TMC4361A_HOME_EVENT_SHIFT); + } else { + // Active low: HOME_REF = 0 indicates negative direction + refConf |= (0b0011 << TMC4361A_HOME_EVENT_SHIFT); + } + // Use stop left as home + refConf |= TMC4361A_STOP_LEFT_IS_HOME_MASK; + } else { // Right switch (RGHT_SW) + if (polarity != 0) { + // Active high + refConf |= (0b0011 << TMC4361A_HOME_EVENT_SHIFT); + } else { + // Active low + refConf |= (0b1100 << TMC4361A_HOME_EVENT_SHIFT); + } + // Use stop right as home (bit 15) + refConf |= (1 << 15); // TMC4361A_STOP_RIGHT_IS_HOME + } + + tmc4361A_writeRegister(icID, TMC4361A_REFERENCE_CONF, refConf); + + // Set HOME_SAFETY_MARGIN + tmc4361A_writeRegister(icID, TMC4361A_HOME_SAFETY_MARGIN, safetyMarginMicrosteps); +} + +// ============================================================================ +// Soft Limit Implementation +// ============================================================================ + +void motor_setSoftLimits(uint8_t icID, int32_t lowerLimitMicrosteps, int32_t upperLimitMicrosteps) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Set virtual stop positions + tmc4361A_writeRegister(icID, TMC4361A_VIRT_STOP_LEFT, lowerLimitMicrosteps); + tmc4361A_writeRegister(icID, TMC4361A_VIRT_STOP_RIGHT, upperLimitMicrosteps); +} + +void motor_enableSoftLimits(uint8_t icID, bool enableLower, bool enableUpper) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Read current REFERENCE_CONF + uint32_t refConf = tmc4361A_readRegister(icID, TMC4361A_REFERENCE_CONF); + + // Configure virtual stop enables (using the official macro definitions) + if (enableLower) { + refConf |= TMC4361A_VIRTUAL_LEFT_LIMIT_EN_MASK; // bit 6 + // Set VIRT_STOP_MODE = 1 for hard stop (consistent with the old API) + refConf |= (1 << TMC4361A_VIRT_STOP_MODE_SHIFT); // bit 8 + } else { + refConf &= ~TMC4361A_VIRTUAL_LEFT_LIMIT_EN_MASK; + } + + if (enableUpper) { + refConf |= TMC4361A_VIRTUAL_RIGHT_LIMIT_EN_MASK; // bit 7 + // Set VIRT_STOP_MODE = 1 for hard stop (consistent with the old API) + refConf |= (1 << TMC4361A_VIRT_STOP_MODE_SHIFT); // bit 8 + } else { + refConf &= ~TMC4361A_VIRTUAL_RIGHT_LIMIT_EN_MASK; + } + + tmc4361A_writeRegister(icID, TMC4361A_REFERENCE_CONF, refConf); +} + +// ============================================================================ +// Advanced Configuration Implementation +// ============================================================================ + +void motor_initABNEncoder(uint8_t icID, uint32_t transitions_per_rev, + uint8_t filter_wait_time, uint8_t filter_exponent, + uint16_t filter_vmean, bool invert_dir) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Set encoder resolution + tmc4361A_writeRegister(icID, TMC4361A_ENC_IN_RES, transitions_per_rev); + + // 2026-05-25 reverted the always-on debug print: legacy Squid software has no mixed ASCII/binary + // parsing and would treat the "ENC_INIT ..." text as response-packet bytes, causing checksum errors + misaligned acks for subsequent commands + // -> cmd 7 (HOME_OR_ZERO) timeout abort. Reverted to DEBUG_PRINT (compiled out under NDEBUG). + DEBUG_PRINT("ENC_INIT icID="); + DEBUG_PRINT(icID); + DEBUG_PRINT(" wrote_ENC_IN_RES="); + DEBUG_PRINT(transitions_per_rev); + DEBUG_PRINT(" readback_ENC_CONST="); + DEBUG_PRINTLN((uint32_t)tmc4361A_readRegister(icID, TMC4361A_ENC_IN_RES)); + + // Set encoder velocity mean filter: + // ENC_VMEAN_FILTER = wait_time | (filter_exp << 8) | (vmean_int << 16) + uint32_t filterVal = (uint32_t)filter_wait_time + | ((uint32_t)filter_exponent << 8) + | ((uint32_t)filter_vmean << 16); + tmc4361A_writeRegister(icID, TMC4361A_ENC_VMEAN_FILTER, filterVal); + + // disable differential encoder input (single-ended ABN encoder) + uint32_t gen_conf = tmc4361A_readRegister(icID, TMC4361A_GENERAL_CONF); + gen_conf |= TMC4361A_DIFF_ENC_IN_DISABLE_MASK; // bit 12 = 1 + tmc4361A_writeRegister(icID, TMC4361A_GENERAL_CONF, gen_conf); + + // Set or clear INVERT_ENC_DIR bit (bit 29 of ENC_IN_CONF) + uint32_t enc_conf = tmc4361A_readRegister(icID, TMC4361A_ENC_IN_CONF); + if (invert_dir) { + enc_conf |= TMC4361A_INVERT_ENC_DIR_MASK; + } else { + enc_conf &= ~TMC4361A_INVERT_ENC_DIR_MASK; + } + tmc4361A_writeRegister(icID, TMC4361A_ENC_IN_CONF, enc_conf); +} + +void motor_initPID(uint8_t icID, uint32_t target_tolerance, uint32_t pid_tolerance, + uint32_t pid_p, uint32_t pid_i, uint32_t pid_d, + uint32_t pid_dclip, uint32_t pid_iclip, uint8_t pid_d_clkdiv) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Closed-loop target tolerance + tmc4361A_writeRegister(icID, TMC4361A_CL_TR_TOLERANCE, target_tolerance); + // PID tolerance + tmc4361A_writeRegister(icID, TMC4361A_PID_TOLERANCE, pid_tolerance); + // PID gains (24-bit each) + tmc4361A_writeRegister(icID, TMC4361A_PID_P, pid_p & 0xFFFFFF); + tmc4361A_writeRegister(icID, TMC4361A_PID_I, pid_i & 0xFFFFFF); + tmc4361A_writeRegister(icID, TMC4361A_PID_D, pid_d & 0xFFFFFF); + // PID velocity clip + tmc4361A_writeRegister(icID, TMC4361A_PID_DV_CLIP, pid_dclip); + // PID integral clip + derivative clock divider + // PID_I_CLIP_WR (0x5D) = iclip | (d_clkdiv << 16) + tmc4361A_writeRegister(icID, TMC4361A_PID_I_CLIP, + pid_iclip | ((uint32_t)pid_d_clkdiv << 16)); +} + +void motor_enablePID(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Set REGULATION_MODUS bits (22-23) of ENC_IN_CONF to 0b10 (PID via BPG0) + uint32_t enc_conf = tmc4361A_readRegister(icID, TMC4361A_ENC_IN_CONF); + enc_conf &= ~TMC4361A_REGULATION_MODUS_MASK; + enc_conf |= (0x02 << TMC4361A_REGULATION_MODUS_SHIFT); + tmc4361A_writeRegister(icID, TMC4361A_ENC_IN_CONF, enc_conf); +} + +void motor_disablePID(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // Clear REGULATION_MODUS bits (22-23) of ENC_IN_CONF to disable PID + uint32_t enc_conf = tmc4361A_readRegister(icID, TMC4361A_ENC_IN_CONF); + enc_conf &= ~TMC4361A_REGULATION_MODUS_MASK; + tmc4361A_writeRegister(icID, TMC4361A_ENC_IN_CONF, enc_conf); +} + +void motor_configStallGuard(uint8_t icID, int8_t threshold, bool filterEnable, bool stopOnStall) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + if (motorParams[icID].driverType == DRIVER_TMC2240) { + // TMC2240: StallGuard4 + // SGT is in bits [22:16] of COOLCONF (0x6D) + tmc2240_fieldWrite(icID, TMC2240_SGT_FIELD, (uint32_t)(threshold & 0x7F)); + // SG4_THRS is in bits [7:0] of SG4_THRS (0x74) + tmc2240_fieldWrite(icID, TMC2240_SG4_FILT_EN_FIELD, filterEnable ? 1 : 0); + } else { + // TMC2660: StallGuard2 + tmc2660_setStallGuardThreshold(icID, threshold); + tmc2660_setStallGuardFilter(icID, filterEnable); + } + + // Configure TMC4361A to react to stall event (consistent with the old API) + if (stopOnStall) { + // Set VSTALL_LIMIT (consistent with the old API) + // 0 = react at any velocity > 0 + tmc4361A_writeRegister(icID, TMC4361A_VSTALL_LIMIT, 0); + + // Enable stop on stall in REFERENCE_CONF (bit 26) + uint32_t refConf = tmc4361A_readRegister(icID, TMC4361A_REFERENCE_CONF); + refConf |= TMC4361A_STOP_ON_STALL_MASK; // Enable stop on stall + refConf &= ~TMC4361A_DRV_AFTER_STALL_MASK; // Disable drive after stall + tmc4361A_writeRegister(icID, TMC4361A_REFERENCE_CONF, refConf); + } +} + +uint8_t motor_readSwitchEvent(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return 0; + + // Read EVENTS register and extract switch events + // STOPL_EVENT is bit 11 (0x0800), STOPR_EVENT is bit 12 (0x1000) + uint32_t events = tmc4361A_readRegister(icID, TMC4361A_EVENTS); + + // Mask and shift to get bits 0 and 1 + events &= (TMC4361A_STOPL_EVENT_MASK | TMC4361A_STOPR_EVENT_MASK); + events >>= TMC4361A_STOPL_EVENT_SHIFT; + + return (uint8_t)(events & 0x03); +} + +void motor_setVelocityInternal(uint8_t icID, int32_t velocityInternal) +{ + if (icID >= MOTOR_IC_COUNT) + return; + + // ======================================================================== + // an implementation exactly matching the old API tmc4361A_setSpeed + // ======================================================================== + + // 1. set the velocity_mode flag (as in the old API: tmc4361A->velocity_mode = true) + motorParams[icID].velocity_mode = true; + + // 2. Clear EVENTS register (reading clears it) + tmc4361A_readRegister(icID, TMC4361A_EVENTS); + + // 3. clear the POSITION and HOLD bits, keep the S-shaped bit + // old API: tmc4361A_rstBits(tmc4361A, TMC4361A_RAMPMODE, TMC4361A_RAMP_POSITION | TMC4361A_RAMP_HOLD); + // if it was originally 0x06 (S-shaped position mode), the result is 0x02 (S-shaped velocity mode) + uint32_t rampModeBefore = tmc4361A_readRegister(icID, TMC4361A_RAMPMODE); + uint32_t rampMode = rampModeBefore & ~(TMC4361A_RAMP_POSITION | TMC4361A_RAMP_HOLD); + tmc4361A_writeRegister(icID, TMC4361A_RAMPMODE, rampMode); + + // 4. Set velocity directly to VMAX (signed value determines direction) + tmc4361A_writeRegister(icID, TMC4361A_VMAX, velocityInternal); + +} + +int32_t motor_readLatchPosition(uint8_t icID) +{ + if (icID >= MOTOR_IC_COUNT) + return 0; + + return tmc4361A_readRegister(icID, TMC4361A_X_LATCH); +} diff --git a/firmware/octoaxes/tmc/motion/MotorControl.h b/firmware/octoaxes/tmc/motion/MotorControl.h new file mode 100644 index 000000000..202fd3e38 --- /dev/null +++ b/firmware/octoaxes/tmc/motion/MotorControl.h @@ -0,0 +1,559 @@ +/* + * MotorControl.h + * + * High-level motion control layer for TMC4361A + TMC2660/TMC2240. + * Provides unified API for motor initialization, motion control, + * and unit conversion. + * + * Created: 2026-01-21 + */ + +#ifndef TMC_MOTION_MOTOR_CONTROL_H_ +#define TMC_MOTION_MOTOR_CONTROL_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================ +// Driver Type Constants +// ============================================================================ + +#define DRIVER_TMC2660 0 +#define DRIVER_TMC2240 1 +#define DRIVER_AUTO 0xFF // auto-detect the driver chip type during init + +// ============================================================================ +// Configuration Structures +// ============================================================================ + +/** + * @brief TMC4361A motion configuration + */ +typedef struct { + uint32_t clockFrequency; // External clock frequency (Hz), typically 16MHz + float screwPitchMM; // Lead screw pitch (mm per revolution) + uint16_t fullStepsPerRev; // Full steps per revolution (typically 200) + uint16_t microsteps; // Microstep resolution (1, 2, 4, ... 256) + float maxVelocityMM; // Maximum velocity (mm/s) + float maxAccelerationMM; // Maximum acceleration (mm/s²) + float maxDecelerationMM; // Maximum deceleration (mm/s²), 0 = same as accel + bool useSShapedRamp; // Use S-shaped ramp (bow parameters) + float astartMM; // Initial acceleration (mm/s²), 0 = disabled + float dfinalMM; // Final deceleration (mm/s²), 0 = same as astart + uint32_t bow1; // Bow parameter 1 (for S-shaped ramp) + uint32_t bow2; // Bow parameter 2 + uint32_t bow3; // Bow parameter 3 + uint32_t bow4; // Bow parameter 4 +} MotionConfig; + +/** + * @brief Motor/driver configuration (supports TMC2660 and TMC2240) + */ +typedef struct { + uint8_t driverType; // DRIVER_TMC2660 (default) or DRIVER_TMC2240 + float rSense; // Sense resistor value (Ohms) + float runCurrentMA; // Peak run current (mA), NOT RMS + float holdCurrentRatio; // Hold current as ratio of run (0.0-1.0) + uint8_t microstepRes; // Microstep resolution (0=256, 1=128, ... 8=1) + bool interpolation; // Enable 256 microstep interpolation + // Chopper parameters (common to TMC2660 and TMC2240) + uint8_t toff; // Chopper off time (1-15) + uint8_t hstrt; // Hysteresis start (0-7) + int8_t hend; // Hysteresis end (-3 to 12) + uint8_t tbl; // Blanking time (0-3) + int8_t stallThreshold; // StallGuard threshold (-64 to 63) + bool stallFilter; // Enable StallGuard filter + // TMC2240-specific parameters (ignored for TMC2660) + bool enableStealthChop; // EN_PWM_MODE (StealthChop) + uint8_t globalScaler; // GLOBAL_SCALER (0=256, 1-255), 0 means full scale + uint8_t iholdDelay; // IHOLDDELAY (0-15) + uint8_t currentRange; // DRV_CONF.CURRENT_RANGE: 0=1A, 1=2A, 2=3A, 3=3A +} MotorConfig; + +/** + * @brief Limit switch configuration + */ +typedef struct { + bool enableLeft; // Enable left limit switch + bool enableRight; // Enable right limit switch + uint8_t leftPolarity; // Left switch polarity (0=active low, 1=active high) + uint8_t rightPolarity; // Right switch polarity + bool leftFlipped; // Swap left/right assignment + bool rightFlipped; + uint8_t homingSwitch; // Which switch to use for homing (0=left, 1=right) + float homeSafetyMarginMM; // Safety margin after homing +} LimitConfig; + +/** + * @brief Combined axis configuration + */ +typedef struct { + MotionConfig motion; + MotorConfig motor; + LimitConfig limits; +} AxisMotionConfig; + +// ============================================================================ +// Motion Parameter Cache (per IC) +// ============================================================================ + +#define MOTOR_IC_COUNT 7 + +// Cached motion parameters for unit conversion and state tracking +typedef struct { + uint32_t clockFrequency; + float screwPitchMM; + uint16_t fullStepsPerRev; + uint16_t microsteps; + float stepsPerMM; // Calculated: (fullStepsPerRev * microsteps) / screwPitchMM + bool initialized; + + // state tracking consistent with the old API velocity_mode + bool velocity_mode; // true when in velocity mode, cleared on moveTo + + // ramp-parameter cache (consistent with the old API rampParam[]) + uint32_t bow1; // BOW1 parameter + uint32_t bow2; // BOW2 parameter + uint32_t bow3; // BOW3 parameter + uint32_t bow4; // BOW4 parameter + uint32_t amax; // Maximum acceleration + uint32_t dmax; // Maximum deceleration + uint32_t astart; // Initial acceleration + uint32_t dfinal; // Final deceleration + int32_t vmax; // Maximum velocity (internal units) + uint8_t driverType; // this axis's driver chip type (DRIVER_TMC2660 / DRIVER_TMC2240) + float rSense; // cached rSense value, used for TMC2660 runtime current calculation + uint8_t currentRange; // cached TMC2240 CURRENT_RANGE, used for runtime current calculation + uint8_t toff; // cached TOFF value, used by enableDriver to restore +} MotorParams; + +extern MotorParams motorParams[MOTOR_IC_COUNT]; + +// ============================================================================ +// Initialization API +// ============================================================================ + +/** + * @brief Initialize motor control subsystem + * Call once at startup before using any motor functions. + */ +void motor_initSubsystem(void); + +/** + * @brief Initialize a motor axis with full configuration + * @param icID IC identifier (0-6) + * @param config Combined axis configuration + * @return true if successful + */ +bool motor_init(uint8_t icID, const AxisMotionConfig *config); + +/** + * @brief Initialize TMC4361A with motion parameters + * @param icID IC identifier + * @param config Motion configuration + * @return true if successful + */ +bool motor_initMotionController(uint8_t icID, const MotionConfig *config); + +/** + * @brief Auto-detect the driver chip type (TMC2240 or TMC2660) + * @param icID IC identifier (TMC4361A must already be reset and communicating normally) + * @return DRIVER_TMC2240 or DRIVER_TMC2660 + */ +uint8_t motor_detectDriverType(uint8_t icID); + +/** + * @brief Initialize TMC2660 driver + * @param icID IC identifier + * @param config Motor configuration + * @return true if successful + */ +bool motor_initDriver(uint8_t icID, const MotorConfig *config); + +/** + * @brief Configure limit switches + * @param icID IC identifier + * @param config Limit switch configuration + */ +void motor_configLimitSwitches(uint8_t icID, const LimitConfig *config); + +/** + * @brief Enable/disable hardware stop on a specific limit switch + * @param icID IC identifier + * @param side LEFT_SW (0x01) or RGHT_SW (0x02) + * @param enable true = enable hardware stop, false = disable + * + * Used during homing to prevent TMC4361A hardware stop from locking out + * subsequent motion commands. STATUS register STOPL/STOPR_ACTIVE_F bits + * still reflect pin state regardless of this setting. + */ +void motor_setHardwareStopEnable(uint8_t icID, uint8_t side, bool enable); + +// ============================================================================ +// Motion Control API +// ============================================================================ + +/** + * @brief Move to absolute position + * @param icID IC identifier + * @param positionMM Target position in mm + */ +void motor_moveToPosition(uint8_t icID, float positionMM); + +/** + * @brief Move relative distance + * @param icID IC identifier + * @param distanceMM Distance to move in mm + */ +void motor_moveByDistance(uint8_t icID, float distanceMM); + +/** + * @brief Move to absolute position in microsteps + * @param icID IC identifier + * @param position Target position in microsteps + * @return true if virtual stop (VSTOPL/VSTOPR) was active before the move; + * caller can use this to decide whether limits need re-enabling later. + * Single SPI STATUS read is done inside this function — callers do + * NOT need to read STATUS themselves. + */ +bool motor_moveToMicrosteps(uint8_t icID, int32_t position); + +/** + * @brief Start velocity mode rotation + * @param icID IC identifier + * @param velocityMM Velocity in mm/s (negative for reverse) + */ +void motor_rotateVelocity(uint8_t icID, float velocityMM); + +/** + * @brief Stop motor (decelerate to stop) + * @param icID IC identifier + */ +void motor_stop(uint8_t icID); + +/** + * @brief Emergency stop (immediate) + * @param icID IC identifier + */ +void motor_emergencyStop(uint8_t icID); + +// ============================================================================ +// Status Query API +// ============================================================================ + +/** + * @brief Check if target position is reached + * @param icID IC identifier + * @return true if target reached + */ +bool motor_isTargetReached(uint8_t icID); + +/** + * @brief Check if motor is running + * @param icID IC identifier + * @return true if motor is moving + */ +bool motor_isRunning(uint8_t icID); + +/** + * @brief Get current position in mm + * @param icID IC identifier + * @return Current position + */ +float motor_getPositionMM(uint8_t icID); + +/** + * @brief Get current position in microsteps + * @param icID IC identifier + * @return Current position + */ +int32_t motor_getPositionMicrosteps(uint8_t icID); + +/** + * @brief Get target position in microsteps + * @param icID IC identifier + * @return Target position + */ +int32_t motor_getTargetMicrosteps(uint8_t icID); + +/** + * @brief Get current velocity in mm/s + * @param icID IC identifier + * @return Current velocity + */ +float motor_getVelocityMM(uint8_t icID); + +/** + * @brief Get current velocity in internal units + * @param icID IC identifier + * @return Current velocity (24.8 fixed point) + */ +int32_t motor_getVelocityInternal(uint8_t icID); + +/** + * @brief Read limit switch status + * @param icID IC identifier + * @return Bit 0 = left, Bit 1 = right + */ +uint8_t motor_readLimitSwitches(uint8_t icID); + +/** + * @brief Read TMC4361A status/event register + * @param icID IC identifier + * @return Status bits + */ +uint32_t motor_readStatus(uint8_t icID); + +/** + * @brief Read TMC4361A event register + * @param icID IC identifier + * @return Event bits + */ +uint32_t motor_readEvents(uint8_t icID); + +// ============================================================================ +// Parameter Setting API +// ============================================================================ + +/** + * @brief Set maximum velocity + * @param icID IC identifier + * @param velocityMM Maximum velocity in mm/s + */ +void motor_setMaxVelocity(uint8_t icID, float velocityMM); + +/** + * @brief Reset RAMPMODE to position mode (S-shaped) + * Call after RESET command or hardware limit trigger to restore normal operation + * @param icID IC identifier + */ +void motor_resetRampMode(uint8_t icID); + +/** + * @brief Set maximum acceleration + * @param icID IC identifier + * @param accelerationMM Maximum acceleration in mm/s² + */ +void motor_setMaxAcceleration(uint8_t icID, float accelerationMM); + +/** + * @brief Set maximum deceleration + * @param icID IC identifier + * @param decelerationMM Maximum deceleration in mm/s² + */ +void motor_setMaxDeceleration(uint8_t icID, float decelerationMM); + +/** + * @brief Set current position (without moving) + * @param icID IC identifier + * @param positionMM Position to set in mm + */ +void motor_setCurrentPosition(uint8_t icID, float positionMM); + +/** + * @brief Set current position in microsteps + * @param icID IC identifier + * @param position Position in microsteps + */ +void motor_setCurrentPositionMicrosteps(uint8_t icID, int32_t position); + +/** + * @brief Set microstep resolution at runtime + * Updates STEP_CONF register and cached stepsPerMM. + * Caller should recalculate motion parameters (VMAX/AMAX/BOW) after calling this. + * @param icID IC identifier + * @param microsteps New microstep resolution (1, 2, 4, ... 256) + */ +void motor_setMicrosteps(uint8_t icID, uint16_t microsteps); + +/** + * @brief Set motor run current + * @param icID IC identifier + * @param currentMA Current in mA + */ +void motor_setRunCurrent(uint8_t icID, float currentMA); + +/** + * @brief Enable/disable motor driver + * @param icID IC identifier + * @param enable true to enable + */ +void motor_enableDriver(uint8_t icID, bool enable); + +// ============================================================================ +// Unit Conversion API +// ============================================================================ + +/** + * @brief Convert mm to microsteps + * @param icID IC identifier + * @param mm Distance in mm + * @return Distance in microsteps + */ +int32_t motor_mmToMicrosteps(uint8_t icID, float mm); + +/** + * @brief Convert microsteps to mm + * @param icID IC identifier + * @param microsteps Distance in microsteps + * @return Distance in mm + */ +float motor_microstepsToMM(uint8_t icID, int32_t microsteps); + +/** + * @brief Convert velocity from mm/s to internal units + * @param icID IC identifier + * @param velocityMM Velocity in mm/s + * @return Velocity in internal units (24.8 fixed point) + */ +int32_t motor_velocityMMToInternal(uint8_t icID, float velocityMM); + +/** + * @brief Convert velocity from internal units to mm/s + * @param icID IC identifier + * @param velocityInternal Velocity in internal units + * @return Velocity in mm/s + */ +float motor_velocityInternalToMM(uint8_t icID, int32_t velocityInternal); + +/** + * @brief Convert acceleration from mm/s² to internal units + * @param icID IC identifier + * @param accelMM Acceleration in mm/s² + * @return Acceleration in internal units + */ +uint32_t motor_accelMMToInternal(uint8_t icID, float accelMM); + +// ============================================================================ +// Homing API +// ============================================================================ + +/** + * @brief Start homing sequence + * @param icID IC identifier + * @param direction Homing direction (-1 or +1) + * @param velocityMM Homing velocity in mm/s + */ +void motor_startHoming(uint8_t icID, int8_t direction, float velocityMM); + +/** + * @brief Set home position (current position becomes reference) + * @param icID IC identifier + * @param positionMM Position value to set as home + */ +void motor_setHomePosition(uint8_t icID, float positionMM); + +/** + * @brief Configure homing limit switch + * @param icID IC identifier + * @param polarity Switch polarity (0=active low, 1=active high) + * @param whichSwitch Which switch (0x01=left, 0x02=right) + * @param safetyMarginMicrosteps Safety margin after homing + */ +void motor_enableHomingLimit(uint8_t icID, uint8_t polarity, uint8_t whichSwitch, + int32_t safetyMarginMicrosteps); + +// ============================================================================ +// Soft Limit API +// ============================================================================ + +/** + * @brief Set soft (virtual) limit positions + * @param icID IC identifier + * @param lowerLimitMicrosteps Lower limit position + * @param upperLimitMicrosteps Upper limit position + */ +void motor_setSoftLimits(uint8_t icID, int32_t lowerLimitMicrosteps, int32_t upperLimitMicrosteps); + +/** + * @brief Enable/disable soft limits + * @param icID IC identifier + * @param enableLower Enable lower limit + * @param enableUpper Enable upper limit + */ +void motor_enableSoftLimits(uint8_t icID, bool enableLower, bool enableUpper); + +// ============================================================================ +// Advanced Configuration API +// ============================================================================ + +/** + * @brief Initialize ABN encoder interface + * @param icID IC identifier + * @param transitions_per_rev Encoder transitions per revolution + * @param filter_wait_time Filter wait time (0-255) + * @param filter_exponent Filter exponent (0-15) + * @param filter_vmean Filter vmean integration (0-65535) + * @param invert_dir Invert encoder direction + */ +void motor_initABNEncoder(uint8_t icID, uint32_t transitions_per_rev, + uint8_t filter_wait_time, uint8_t filter_exponent, + uint16_t filter_vmean, bool invert_dir); + +/** + * @brief Initialize PID parameters (write to TMC4361A registers) + * @param icID IC identifier + * @param target_tolerance Closed-loop target tolerance + * @param pid_tolerance PID tolerance + * @param pid_p Proportional gain + * @param pid_i Integral gain + * @param pid_d Derivative gain + * @param pid_dclip PID velocity clip + * @param pid_iclip PID integral clip + * @param pid_d_clkdiv PID derivative clock divider + */ +void motor_initPID(uint8_t icID, uint32_t target_tolerance, uint32_t pid_tolerance, + uint32_t pid_p, uint32_t pid_i, uint32_t pid_d, + uint32_t pid_dclip, uint32_t pid_iclip, uint8_t pid_d_clkdiv); + +/** + * @brief Enable PID control mode + * @param icID IC identifier + */ +void motor_enablePID(uint8_t icID); + +/** + * @brief Disable PID control mode + * @param icID IC identifier + */ +void motor_disablePID(uint8_t icID); + +/** + * @brief Configure StallGuard parameters + * @param icID IC identifier + * @param threshold StallGuard threshold (-64 to 63) + * @param filterEnable Enable StallGuard filter + * @param stopOnStall Stop motor when stall detected + */ +void motor_configStallGuard(uint8_t icID, int8_t threshold, bool filterEnable, bool stopOnStall); + +/** + * @brief Read switch event register (clears on read) + * @param icID IC identifier + * @return Switch event bits + */ +uint8_t motor_readSwitchEvent(uint8_t icID); + +/** + * @brief Set velocity directly in internal units (for homing) + * @param icID IC identifier + * @param velocityInternal Velocity in internal units (signed, direction included) + */ +void motor_setVelocityInternal(uint8_t icID, int32_t velocityInternal); + +/** + * @brief Read latched position (captured on limit switch event) + * @param icID IC identifier + * @return Latched position in microsteps + */ +int32_t motor_readLatchPosition(uint8_t icID); + +#ifdef __cplusplus +} +#endif + +#endif /* TMC_MOTION_MOTOR_CONTROL_H_ */ diff --git a/firmware/octoaxes/trigger.cpp b/firmware/octoaxes/trigger.cpp new file mode 100644 index 000000000..8c9bc42fc --- /dev/null +++ b/firmware/octoaxes/trigger.cpp @@ -0,0 +1,127 @@ +#include "trigger.h" +#include "build_opt.h" +#include "illumination.h" + +// ============================================================================= +// State-variable definitions +// ============================================================================= + +bool trigger_output_level[NUM_TRIGGER_CHANNELS]; +bool control_strobe[NUM_TRIGGER_CHANNELS]; +bool strobe_output_level[NUM_TRIGGER_CHANNELS]; +bool strobe_on[NUM_TRIGGER_CHANNELS]; +unsigned long strobe_delay_us[NUM_TRIGGER_CHANNELS]; +uint32_t illumination_on_time_us[NUM_TRIGGER_CHANNELS]; +unsigned long timestamp_trigger_rising_edge[NUM_TRIGGER_CHANNELS]; +volatile uint8_t trigger_mode = TRIGGER_MODE_NORMAL; + +// Joystick state +bool joystick_button_pressed = false; +unsigned long joystick_button_pressed_timestamp = 0; + +// Strobe timer +static IntervalTimer strobeTimer; + +// ============================================================================= +// Initialization +// ============================================================================= + +void trigger_init() +{ + // initialize the trigger pins: OUTPUT + HIGH (idle is high, negative-pulse triggered) + for (int i = 0; i < NUM_TRIGGER_CHANNELS; i++) { + pinMode(camera_trigger_pins[i], OUTPUT); + digitalWrite(camera_trigger_pins[i], HIGH); + } + + // initialize the state arrays + for (int i = 0; i < NUM_TRIGGER_CHANNELS; i++) { + trigger_output_level[i] = HIGH; + control_strobe[i] = false; + strobe_output_level[i] = LOW; + strobe_on[i] = false; + strobe_delay_us[i] = 0; + illumination_on_time_us[i] = 0; + timestamp_trigger_rising_edge[i] = 0; + } + + trigger_mode = TRIGGER_MODE_NORMAL; + + // start the strobe timer (100us interval) + strobeTimer.begin(ISR_strobeTimer, STROBE_TIMER_INTERVAL_us); + + DEBUG_PRINTLN("Trigger system initialized"); +} + +// ============================================================================= +// main-loop update: manage trigger-pulse recovery +// ============================================================================= + +void trigger_update() +{ + unsigned long now = micros(); + + for (int i = 0; i < NUM_TRIGGER_CHANNELS; i++) { + // only process channels that have been triggered (LOW) + if (trigger_output_level[i] == LOW) { + if (trigger_mode == TRIGGER_MODE_NORMAL) { + // mode 0: restore HIGH after a fixed 50us pulse width + if (now - timestamp_trigger_rising_edge[i] >= TRIGGER_PULSE_LENGTH_us) { + digitalWrite(camera_trigger_pins[i], HIGH); + trigger_output_level[i] = HIGH; + } + } else { + // mode 1: pulse width = strobe_delay + illumination_on_time + unsigned long pulse_duration = strobe_delay_us[i] + illumination_on_time_us[i]; + if (now - timestamp_trigger_rising_edge[i] >= pulse_duration) { + digitalWrite(camera_trigger_pins[i], HIGH); + trigger_output_level[i] = HIGH; + } + } + } + } +} + +// ============================================================================= +// strobe timer ISR (100us interval) +// ============================================================================= + +void ISR_strobeTimer() +{ + unsigned long now = micros(); + + for (int i = 0; i < NUM_TRIGGER_CHANNELS; i++) { + // only process triggered channels that have strobe control enabled + if (!control_strobe[i] || trigger_output_level[i] == HIGH) + continue; + + unsigned long elapsed = now - timestamp_trigger_rising_edge[i]; + + if (illumination_on_time_us[i] <= 30000) { + // short exposure (<= 30ms): synchronous mode + // wait strobe_delay then turn on the light, keep it on for illumination_on_time, then turn off + if (!strobe_on[i] && elapsed >= strobe_delay_us[i]) { + turn_on_illumination(); + strobe_on[i] = true; + // short exposure uses delayMicroseconds for precise control + delayMicroseconds(illumination_on_time_us[i]); + turn_off_illumination(); + strobe_on[i] = false; + control_strobe[i] = false; // one strobe done, clear the flag + } + } else { + // long exposure (> 30ms): asynchronous mode, split into two steps + if (!strobe_on[i] && elapsed >= strobe_delay_us[i]) { + // step 1: turn on the light + turn_on_illumination(); + strobe_on[i] = true; + } else if (strobe_on[i] && + elapsed >= strobe_delay_us[i] + illumination_on_time_us[i]) { + // step 2: turn off the light + turn_off_illumination(); + strobe_on[i] = false; + control_strobe[i] = false; // one strobe done, clear the flag + } + } + } +} diff --git a/firmware/octoaxes/trigger.h b/firmware/octoaxes/trigger.h new file mode 100644 index 000000000..e6239ea54 --- /dev/null +++ b/firmware/octoaxes/trigger.h @@ -0,0 +1,59 @@ +#ifndef TRIGGER_H +#define TRIGGER_H + +#include +#include "config.h" + +// ============================================================================= +// Trigger-mode constants +// ============================================================================= + +const uint8_t TRIGGER_MODE_NORMAL = 0; // fixed 50us pulse +const uint8_t TRIGGER_MODE_LEVEL = 1; // level trigger (strobe_delay + on_time) + +// Trigger-pulse parameters +const int TRIGGER_PULSE_LENGTH_us = 50; +const int NUM_TRIGGER_CHANNELS = 4; + +// Strobe timer interval +const int STROBE_TIMER_INTERVAL_us = 100; + +// Camera-trigger pin mapping +const int camera_trigger_pins[NUM_TRIGGER_CHANNELS] = { + Pins::CAMERA_TRIGGER_1, // pin 29 + Pins::CAMERA_TRIGGER_2, // pin 30 + Pins::CAMERA_TRIGGER_3, // pin 31 + Pins::CAMERA_TRIGGER_4 // pin 32 +}; + +// ============================================================================= +// State variables (extern declarations, defined in trigger.cpp) +// ============================================================================= + +extern bool trigger_output_level[NUM_TRIGGER_CHANNELS]; +extern bool control_strobe[NUM_TRIGGER_CHANNELS]; +extern bool strobe_output_level[NUM_TRIGGER_CHANNELS]; +extern bool strobe_on[NUM_TRIGGER_CHANNELS]; +extern unsigned long strobe_delay_us[NUM_TRIGGER_CHANNELS]; +extern uint32_t illumination_on_time_us[NUM_TRIGGER_CHANNELS]; +extern unsigned long timestamp_trigger_rising_edge[NUM_TRIGGER_CHANNELS]; +extern volatile uint8_t trigger_mode; + +// Joystick state +extern bool joystick_button_pressed; +extern unsigned long joystick_button_pressed_timestamp; + +// ============================================================================= +// API +// ============================================================================= + +// Initialize the trigger system: pins, state arrays, timer +void trigger_init(); + +// Called from the main loop: manage trigger-pulse recovery (HIGH level) +void trigger_update(); + +// Timer interrupt callback: manage strobe-illumination timing +void ISR_strobeTimer(); + +#endif // TRIGGER_H diff --git a/firmware/octoaxes/utils.cpp b/firmware/octoaxes/utils.cpp new file mode 100644 index 000000000..a8c7cb6ad --- /dev/null +++ b/firmware/octoaxes/utils.cpp @@ -0,0 +1,32 @@ +#include + +#include "utils.h" +#include "build_opt.h" + +const unsigned long SETUP_LED_ON_IF_TRIPPED_DURATION = 200; +const unsigned long SETUP_LED_OFF_DURATION = 200; + +void setLedOff() +{ + digitalWrite(LED_BUILTIN,LOW); + delay(SETUP_LED_OFF_DURATION); +} + +void setLedOn(unsigned long duration) +{ + digitalWrite(LED_BUILTIN,HIGH); + delay(duration); +} + +void initializeStartupLED() +{ +#ifdef ENABLE_LED_INDICATOR + pinMode(LED_BUILTIN,OUTPUT); + setLedOff(); + setLedOn(SETUP_LED_ON_IF_TRIPPED_DURATION); + setLedOff(); + setLedOn(SETUP_LED_ON_IF_TRIPPED_DURATION); + setLedOff(); + setLedOn(SETUP_LED_ON_IF_TRIPPED_DURATION); +#endif +} diff --git a/firmware/octoaxes/utils.h b/firmware/octoaxes/utils.h new file mode 100644 index 000000000..502ab9f3a --- /dev/null +++ b/firmware/octoaxes/utils.h @@ -0,0 +1,6 @@ +#ifndef INCLUDED_UTILS_H +#define INCLUDED_UTILS_H + +void initializeStartupLED(); + +#endif /* INCLUDED_UTILS_H */ From 6d03417211e4eb3d09b3bf219c511888d3b68ab0 Mon Sep 17 00:00:00 2001 From: "kevin.wang" Date: Wed, 1 Jul 2026 13:22:43 +0800 Subject: [PATCH 2/2] ci(firmware): build octoaxes firmware in the firmware workflow Add a `pio run -e teensy41` step for firmware/octoaxes so the new multi-axis controller firmware is compile-checked on every push/PR, alongside the controller and joystick builds. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/firmware.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/firmware.yml b/.github/workflows/firmware.yml index 7d0cb3ea8..a78d48cd7 100644 --- a/.github/workflows/firmware.yml +++ b/.github/workflows/firmware.yml @@ -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