/* * stepper.cpp - stepper motor controls * This file is part of the g2core project * * Copyright (c) 2010 - 2016 Alden S. Hart, Jr. * Copyright (c) 2013 - 2016 Robert Giseburt * * This file ("the software") is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2 as published by the * Free Software Foundation. You should have received a copy of the GNU General Public * License, version 2 along with the software. If not, see . * * As a special exception, you may use this file as part of a software library without * restriction. Specifically, if other files instantiate templates or use macros or * inline functions from this file, or you compile this file and link it with other * files to produce an executable, this file does not by itself cause the resulting * executable to be covered by the GNU General Public License. This exception does not * however invalidate any other reasons why the executable file might be covered by the * GNU General Public License. * * THE SOFTWARE IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY * WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* This module provides the low-level stepper drivers and some related functions. * See stepper.h for a detailed explanation of this module. */ #include "g2core.h" #include "config.h" #include "stepper.h" #include "encoder.h" #include "planner.h" #include "hardware.h" #include "text_parser.h" #include "util.h" #include "controller.h" #include "xio.h" /**** Debugging output with semihosting ****/ #include "MotateDebug.h" // Unless debugging, this should always read "#if 0 && ..." // DON'T COMMIT with anything else! #if 0 && (IN_DEBUGGER == 1) template void stepper_debug(const char (&str)[len]) { Motate::debug.write(str); }; #else template void stepper_debug(const char (&str)[len]) { ; }; #endif /**** Allocate structures ****/ stConfig_t st_cfg; stPrepSingleton_t st_pre; static stRunSingleton_t st_run HOT_DATA; /**** Static functions ****/ static void _load_move(void); // handy macro //#define _f_to_period(f) (uint16_t)((float)F_CPU / (float)f) /**** Setup motate ****/ using namespace Motate; extern OutputPin debug_pin1; extern OutputPin debug_pin2; extern OutputPin debug_pin3; //extern OutputPin debug_pin4; dda_timer_type dda_timer {kTimerUpToMatch, FREQUENCY_DDA}; // stepper pulse generation exec_timer_type exec_timer; // triggers calculation of next+1 stepper segment fwd_plan_timer_type fwd_plan_timer; // triggers planning of next block // SystickEvent for handling dweels (must be registered before it is active) Motate::SysTickEvent dwell_systick_event {[&] { if (--st_run.dwell_ticks_downcount == 0) { SysTickTimer.unregisterEvent(&dwell_systick_event); _load_move(); // load the next move at the current interrupt level } }, nullptr}; /************************************************************************************ **** CODE ************************************************************************** ************************************************************************************/ /* * stepper_init() - initialize stepper motor subsystem * stepper_reset() - reset stepper motor subsystem * * Notes: * - This init requires sys_init() to be run beforehand * - microsteps are setup during config_init() * - motor polarity is setup during config_init() * - high level interrupts must be enabled in main() once all inits are complete */ /* NOTE: This is the bare code that the Motate timer calls replace. * NB: requires: #include * * REG_TC1_WPMR = 0x54494D00; // enable write to registers * TC_Configure(TC_BLOCK_DDA, TC_CHANNEL_DDA, TC_CMR_DDA); * REG_RC_DDA = TC_RC_DDA; // set frequency * REG_IER_DDA = TC_IER_DDA; // enable interrupts * NVIC_EnableIRQ(TC_IRQn_DDA); * pmc_enable_periph_clk(TC_ID_DDA); * TC_Start(TC_BLOCK_DDA, TC_CHANNEL_DDA); */ void stepper_init() { memset(&st_run, 0, sizeof(st_run)); // clear all values, pointers and status memset(&st_pre, 0, sizeof(st_pre)); // clear all values, pointers and status stepper_init_assertions(); // setup DDA timer // Longer duty cycles stretch ON pulses but 75% is about the upper limit and about // optimal for 200 KHz DDA clock before the time in the OFF cycle is too short. // If you need more pulse width you need to drop the DDA clock rate dda_timer.setInterrupts(kInterruptOnOverflow | kInterruptPriorityHighest); // setup software interrupt exec timer & initial condition exec_timer.setInterrupts(kInterruptOnSoftwareTrigger | kInterruptPriorityHigh); st_pre.buffer_state = PREP_BUFFER_OWNED_BY_EXEC; // setup software interrupt forward plan timer & initial condition fwd_plan_timer.setInterrupts(kInterruptOnSoftwareTrigger | kInterruptPriorityMedium); // setup motor power levels and apply power level to stepper drivers for (uint8_t motor=0; motorsetPowerLevel(st_cfg.mot[motor].power_level); st_run.mot[motor].power_level_dynamic = st_cfg.mot[motor].power_level; } board_stepper_init(); stepper_reset(); // reset steppers to known state } /* * stepper_reset() - reset stepper internals * * Used to initialize stepper and also to halt movement */ void stepper_reset() { dda_timer.stop(); // stop all movement st_run.dda_ticks_downcount = 0; // signal the runtime is not busy st_run.dwell_ticks_downcount = 0; st_pre.buffer_state = PREP_BUFFER_OWNED_BY_EXEC; // set to EXEC or it won't restart for (uint8_t motor=0; motorperiodicCheck(have_actually_stopped); } return (STAT_OK); } /****************************** * Interrupt Service Routines * ******************************/ /***** Stepper Interrupt Service Routine ************************************************ * ISR - DDA timer interrupt routine - service ticks from DDA timer */ /* * The DDA timer interrupt does this: * - fire on overflow * - clear interrupt condition * - clear all step pins - this clears those the were set during the previous interrupt * - if downcount == 0 and stop the timer and exit * - run the DDA for each channel * - decrement the downcount - if it reaches zero load the next segment * * Note that the motor_N.step.isNull() tests are compile-time tests, not run-time tests. * If motor_N is not defined that if{} clause (i.e. that motor) drops out of the complied code. */ namespace Motate { // Must define timer interrupts inside the Motate namespace template<> void dda_timer_type::interrupt() HOT_FUNC; template<> void dda_timer_type::interrupt() { dda_timer.getInterruptCause(); // clear interrupt condition // clear all steps from the previous interrupt // for (uint8_t motor=0; motorstepEnd(); // } // loop unrolled version (it's actually faster) motor_1.stepEnd(); motor_2.stepEnd(); #if MOTORS > 2 motor_3.stepEnd(); #endif #if MOTORS > 3 motor_4.stepEnd(); #endif #if MOTORS > 4 motor_5.stepEnd(); #endif #if MOTORS > 5 motor_6.stepEnd(); #endif // process last DDA tick after end of segment if (st_run.dda_ticks_downcount == 0) { dda_timer.stop(); // turn it off or it will keep stepping out the last segment return; } // The following code would work, but it's faster on the M3 to loop unroll it. Perhaps not on the M7 // for (uint8_t motor=0; motor 0) { // Motors[motor]->stepStart(); // turn step bit on // st_run.mot[motor].substep_accumulator -= st_run.dda_ticks_X_substeps; // INCREMENT_ENCODER(motor); // } // } // process DDAs for each motor if ((st_run.mot[MOTOR_1].substep_accumulator += st_run.mot[MOTOR_1].substep_increment) > 0) { motor_1.stepStart(); // turn step bit on st_run.mot[MOTOR_1].substep_accumulator -= st_run.dda_ticks_X_substeps; INCREMENT_ENCODER(MOTOR_1); } if ((st_run.mot[MOTOR_2].substep_accumulator += st_run.mot[MOTOR_2].substep_increment) > 0) { motor_2.stepStart(); // turn step bit on st_run.mot[MOTOR_2].substep_accumulator -= st_run.dda_ticks_X_substeps; INCREMENT_ENCODER(MOTOR_2); } #if MOTORS > 2 if ((st_run.mot[MOTOR_3].substep_accumulator += st_run.mot[MOTOR_3].substep_increment) > 0) { motor_3.stepStart(); // turn step bit on st_run.mot[MOTOR_3].substep_accumulator -= st_run.dda_ticks_X_substeps; INCREMENT_ENCODER(MOTOR_3); } #endif #if MOTORS > 3 if ((st_run.mot[MOTOR_4].substep_accumulator += st_run.mot[MOTOR_4].substep_increment) > 0) { motor_4.stepStart(); // turn step bit on st_run.mot[MOTOR_4].substep_accumulator -= st_run.dda_ticks_X_substeps; INCREMENT_ENCODER(MOTOR_4); } #endif #if MOTORS > 4 if ((st_run.mot[MOTOR_5].substep_accumulator += st_run.mot[MOTOR_5].substep_increment) > 0) { motor_5.stepStart(); // turn step bit on st_run.mot[MOTOR_5].substep_accumulator -= st_run.dda_ticks_X_substeps; INCREMENT_ENCODER(MOTOR_5); } #endif #if MOTORS > 5 if ((st_run.mot[MOTOR_6].substep_accumulator += st_run.mot[MOTOR_6].substep_increment) > 0) { motor_6.stepStart(); // turn step bit on st_run.mot[MOTOR_6].substep_accumulator -= st_run.dda_ticks_X_substeps; INCREMENT_ENCODER(MOTOR_6); } #endif // Process end of segment. // One more interrupt will occur to turn of any pulses set in this pass. if (--st_run.dda_ticks_downcount == 0) { _load_move(); // load the next move at the current interrupt level } } // MOTATE_TIMER_INTERRUPT } // namespace Motate /**************************************************************************************** * Exec sequencing code - computes and prepares next load segment * st_request_exec_move() - SW interrupt to request to execute a move * exec_timer interrupt - interrupt handler for calling exec function */ void st_request_exec_move() { stepper_debug("e"); exec_timer.setInterruptPending(); stepper_debug("!\n"); } namespace Motate { // Define timer inside Motate namespace template<> void exec_timer_type::interrupt() HOT_FUNC; template<> void exec_timer_type::interrupt() { exec_timer.getInterruptCause(); // clears the interrupt condition if (st_pre.buffer_state == PREP_BUFFER_OWNED_BY_EXEC) { stepper_debug("E>"); if (mp_exec_move() != STAT_NOOP) { stepper_debug("E+\n"); st_pre.buffer_state = PREP_BUFFER_OWNED_BY_LOADER; // flip it back st_request_load_move(); return; } stepper_debug("E-\n"); } } } // namespace Motate void st_request_forward_plan() { stepper_debug("p"); fwd_plan_timer.setInterruptPending(); } namespace Motate { // Define timer inside Motate namespace template<> void fwd_plan_timer_type::interrupt() HOT_FUNC; template<> void fwd_plan_timer_type::interrupt() { fwd_plan_timer.getInterruptCause(); // clears the interrupt condition stepper_debug("P>"); if (mp_forward_plan() != STAT_NOOP) { // We now have a move to exec. stepper_debug("P+\n"); st_request_exec_move(); return; } stepper_debug("P-\n"); } } // namespace Motate /**************************************************************************************** * Loader sequencing code * st_request_load_move() - fires a software interrupt (timer) to request to load a move * load_move interrupt - interrupt handler for running the loader * * _load_move() can only be called be called from an ISR at the same or higher level as * the DDA or dwell ISR. A software interrupt has been provided to allow a non-ISR to * request a load (see st_request_load_move()) */ void st_request_load_move() { if (st_runtime_isbusy()) { // don't request a load if the runtime is busy return; } stepper_debug("l"); if (st_pre.buffer_state == PREP_BUFFER_OWNED_BY_LOADER) { // bother interrupting stepper_debug("_"); _load_move(); } } /**************************************************************************************** * _load_move() - Dequeue move and load into stepper runtime structure * * This routine can only be called be called from an ISR at the same or * higher level as the DDA or dwell ISR. A software interrupt has been * provided to allow a non-ISR to request a load (st_request_load_move()) * * In aline() code: * - All axes must set steps and compensate for out-of-range pulse phasing. * - If axis has 0 steps the direction setting can be omitted * - If axis has 0 steps the motor power must be set accord to the power mode */ static void _load_move() { // Be aware that dda_ticks_downcount must equal zero for the loader to run. // So the initial load must also have this set to zero as part of initialization if (st_runtime_isbusy()) { return; // exit if the runtime is busy } if (st_pre.buffer_state != PREP_BUFFER_OWNED_BY_LOADER) { // if there are no moves to load... if (cm.motion_state == MOTION_RUN) { #if IN_DEBUGGER == 1 //#warning debbugger REQUIRED for running this firmware! // __asm__("BKPT"); // attempted to _load_move with PREP_BUFFER_OWNED_BY_EXEC and cm.motion_state == MOTION_RUN #endif st_request_exec_move(); return; } // ...start motor power timeouts // for (uint8_t motor = MOTOR_1; motor < MOTORS; motor++) { // Motors[motor]->motionStopped(); // } // loop unrolled version motor_1.motionStopped(); // ...start motor power timeouts motor_2.motionStopped(); // ...start motor power timeouts #if (MOTORS > 2) motor_3.motionStopped(); // ...start motor power timeouts #endif #if (MOTORS > 3) motor_4.motionStopped(); // ...start motor power timeouts #endif #if (MOTORS > 4) motor_5.motionStopped(); // ...start motor power timeouts #endif #if (MOTORS > 5) motor_6.motionStopped(); // ...start motor power timeouts #endif stepper_debug("•"); return; } // if (st_pre.buffer_state != PREP_BUFFER_OWNED_BY_LOADER) stepper_debug("^"); // handle aline loads first (most common case) NB: there are no more lines, only alines if (st_pre.block_type == BLOCK_TYPE_ALINE) { //**** setup the new segment **** st_run.dda_ticks_downcount = st_pre.dda_ticks; st_run.dda_ticks_X_substeps = st_pre.dda_ticks_X_substeps; // INLINED VERSION: 4.3us //**** MOTOR_1 LOAD **** // These sections are somewhat optimized for execution speed. The whole load operation // is supposed to take < 5 uSec (Arm M3 core). Be careful if you mess with this. // the following if() statement sets the runtime substep increment value or zeroes it if ((st_run.mot[MOTOR_1].substep_increment = st_pre.mot[MOTOR_1].substep_increment) != 0) { // NB: If motor has 0 steps the following is all skipped. This ensures that state comparisons // always operate on the last segment actually run by this motor, regardless of how many // segments it may have been inactive in between. // Apply accumulator correction if the time base has changed since previous segment if (st_pre.mot[MOTOR_1].accumulator_correction_flag == true) { st_pre.mot[MOTOR_1].accumulator_correction_flag = false; st_run.mot[MOTOR_1].substep_accumulator *= st_pre.mot[MOTOR_1].accumulator_correction; } // Detect direction change and if so: // Set the direction bit in hardware. // Compensate for direction change by flipping substep accumulator value about its midpoint. if (st_pre.mot[MOTOR_1].direction != st_pre.mot[MOTOR_1].prev_direction) { st_pre.mot[MOTOR_1].prev_direction = st_pre.mot[MOTOR_1].direction; st_run.mot[MOTOR_1].substep_accumulator = -(st_run.dda_ticks_X_substeps + st_run.mot[MOTOR_1].substep_accumulator); motor_1.setDirection(st_pre.mot[MOTOR_1].direction); } // Enable the stepper and start/update motor power management motor_1.enable(); SET_ENCODER_STEP_SIGN(MOTOR_1, st_pre.mot[MOTOR_1].step_sign); } else { // Motor has 0 steps; might need to energize motor for power mode processing motor_1.motionStopped(); } // accumulate counted steps to the step position and zero out counted steps for the segment currently being loaded ACCUMULATE_ENCODER(MOTOR_1); #if (MOTORS >= 2) if ((st_run.mot[MOTOR_2].substep_increment = st_pre.mot[MOTOR_2].substep_increment) != 0) { if (st_pre.mot[MOTOR_2].accumulator_correction_flag == true) { st_pre.mot[MOTOR_2].accumulator_correction_flag = false; st_run.mot[MOTOR_2].substep_accumulator *= st_pre.mot[MOTOR_2].accumulator_correction; } if (st_pre.mot[MOTOR_2].direction != st_pre.mot[MOTOR_2].prev_direction) { st_pre.mot[MOTOR_2].prev_direction = st_pre.mot[MOTOR_2].direction; st_run.mot[MOTOR_2].substep_accumulator = -(st_run.dda_ticks_X_substeps + st_run.mot[MOTOR_2].substep_accumulator); motor_2.setDirection(st_pre.mot[MOTOR_2].direction); } motor_2.enable(); SET_ENCODER_STEP_SIGN(MOTOR_2, st_pre.mot[MOTOR_2].step_sign); } else { motor_2.motionStopped(); } ACCUMULATE_ENCODER(MOTOR_2); #endif #if (MOTORS >= 3) if ((st_run.mot[MOTOR_3].substep_increment = st_pre.mot[MOTOR_3].substep_increment) != 0) { if (st_pre.mot[MOTOR_3].accumulator_correction_flag == true) { st_pre.mot[MOTOR_3].accumulator_correction_flag = false; st_run.mot[MOTOR_3].substep_accumulator *= st_pre.mot[MOTOR_3].accumulator_correction; } if (st_pre.mot[MOTOR_3].direction != st_pre.mot[MOTOR_3].prev_direction) { st_pre.mot[MOTOR_3].prev_direction = st_pre.mot[MOTOR_3].direction; st_run.mot[MOTOR_3].substep_accumulator = -(st_run.dda_ticks_X_substeps + st_run.mot[MOTOR_3].substep_accumulator); motor_3.setDirection(st_pre.mot[MOTOR_3].direction); } motor_3.enable(); SET_ENCODER_STEP_SIGN(MOTOR_3, st_pre.mot[MOTOR_3].step_sign); } else { motor_3.motionStopped(); } ACCUMULATE_ENCODER(MOTOR_3); #endif #if (MOTORS >= 4) if ((st_run.mot[MOTOR_4].substep_increment = st_pre.mot[MOTOR_4].substep_increment) != 0) { if (st_pre.mot[MOTOR_4].accumulator_correction_flag == true) { st_pre.mot[MOTOR_4].accumulator_correction_flag = false; st_run.mot[MOTOR_4].substep_accumulator *= st_pre.mot[MOTOR_4].accumulator_correction; } if (st_pre.mot[MOTOR_4].direction != st_pre.mot[MOTOR_4].prev_direction) { st_pre.mot[MOTOR_4].prev_direction = st_pre.mot[MOTOR_4].direction; st_run.mot[MOTOR_4].substep_accumulator = -(st_run.dda_ticks_X_substeps + st_run.mot[MOTOR_4].substep_accumulator); motor_4.setDirection(st_pre.mot[MOTOR_4].direction); } motor_4.enable(); SET_ENCODER_STEP_SIGN(MOTOR_4, st_pre.mot[MOTOR_4].step_sign); } else { motor_4.motionStopped(); } ACCUMULATE_ENCODER(MOTOR_4); #endif #if (MOTORS >= 5) if ((st_run.mot[MOTOR_5].substep_increment = st_pre.mot[MOTOR_5].substep_increment) != 0) { if (st_pre.mot[MOTOR_5].accumulator_correction_flag == true) { st_pre.mot[MOTOR_5].accumulator_correction_flag = false; st_run.mot[MOTOR_5].substep_accumulator *= st_pre.mot[MOTOR_5].accumulator_correction; } if (st_pre.mot[MOTOR_5].direction != st_pre.mot[MOTOR_5].prev_direction) { st_pre.mot[MOTOR_5].prev_direction = st_pre.mot[MOTOR_5].direction; st_run.mot[MOTOR_5].substep_accumulator = -(st_run.dda_ticks_X_substeps + st_run.mot[MOTOR_5].substep_accumulator); motor_5.setDirection(st_pre.mot[MOTOR_5].direction); } motor_5.enable(); SET_ENCODER_STEP_SIGN(MOTOR_5, st_pre.mot[MOTOR_5].step_sign); } else { motor_5.motionStopped(); } ACCUMULATE_ENCODER(MOTOR_5); #endif #if (MOTORS >= 6) if ((st_run.mot[MOTOR_6].substep_increment = st_pre.mot[MOTOR_6].substep_increment) != 0) { if (st_pre.mot[MOTOR_6].accumulator_correction_flag == true) { st_pre.mot[MOTOR_6].accumulator_correction_flag = false; st_run.mot[MOTOR_6].substep_accumulator *= st_pre.mot[MOTOR_6].accumulator_correction; } if (st_pre.mot[MOTOR_6].direction != st_pre.mot[MOTOR_6].prev_direction) { st_pre.mot[MOTOR_6].prev_direction = st_pre.mot[MOTOR_6].direction; st_run.mot[MOTOR_6].substep_accumulator = -(st_run.dda_ticks_X_substeps + st_run.mot[MOTOR_6].substep_accumulator); motor_6.setDirection(st_pre.mot[MOTOR_6].direction); } motor_6.enable(); SET_ENCODER_STEP_SIGN(MOTOR_6, st_pre.mot[MOTOR_6].step_sign); } else { motor_6.motionStopped(); } ACCUMULATE_ENCODER(MOTOR_6); #endif //**** do this last **** dda_timer.start(); // start the DDA timer if not already running // handle dwells and commands } else if (st_pre.block_type == BLOCK_TYPE_DWELL) { st_run.dwell_ticks_downcount = st_pre.dwell_ticks; // We now use SysTick events to handle dwells SysTickTimer.registerEvent(&dwell_systick_event); // handle synchronous commands } else if (st_pre.block_type == BLOCK_TYPE_COMMAND) { mp_runtime_command(st_pre.bf); } // else null - which is okay in many cases // all other cases drop to here (e.g. Null moves after Mcodes skip to here) st_pre.block_type = BLOCK_TYPE_NULL; st_pre.buffer_state = PREP_BUFFER_OWNED_BY_EXEC; // we are done with the prep buffer - flip the flag back st_request_exec_move(); // exec and prep next move } /*********************************************************************************** * st_prep_line() - Prepare the next move for the loader * * This function does the math on the next pulse segment and gets it ready for * the loader. It deals with all the DDA optimizations and timer setups so that * loading can be performed as rapidly as possible. It works in joint space * (motors) and it works in steps, not length units. All args are provided as * floats and converted to their appropriate integer types for the loader. * * Args: * - travel_steps[] are signed relative motion in steps for each motor. Steps are * floats that typically have fractional values (fractional steps). The sign * indicates direction. Motors that are not in the move should be 0 steps on input. * * - following_error[] is a vector of measured errors to the step count. Used for correction. * * - segment_time - how many minutes the segment should run. If timing is not * 100% accurate this will affect the move velocity, but not the distance traveled. * * NOTE: Many of the expressions are sensitive to casting and execution order to avoid long-term * accuracy errors due to floating point round off. One earlier failed attempt was: * dda_ticks_X_substeps = (int32_t)((microseconds/1000000) * f_dda * dda_substeps); */ stat_t st_prep_line(float travel_steps[], float following_error[], float segment_time) { stepper_debug("😶"); // trap assertion failures and other conditions that would prevent queuing the line if (st_pre.buffer_state != PREP_BUFFER_OWNED_BY_EXEC) { // never supposed to happen return (cm_panic(STAT_INTERNAL_ERROR, "st_prep_line() prep sync error")); } else if (isinf(segment_time)) { // never supposed to happen return (cm_panic(STAT_PREP_LINE_MOVE_TIME_IS_INFINITE, "st_prep_line()")); } else if (isnan(segment_time)) { // never supposed to happen return (cm_panic(STAT_PREP_LINE_MOVE_TIME_IS_NAN, "st_prep_line()")); // } else if (segment_time < EPSILON) { // return (STAT_MINIMUM_TIME_MOVE); } // setup segment parameters // - dda_ticks is the integer number of DDA clock ticks needed to play out the segment // - ticks_X_substeps is the maximum depth of the DDA accumulator (as a negative number) //st_pre.dda_period = _f_to_period(FREQUENCY_DDA); // FYI: this is a constant st_pre.dda_ticks = (int32_t)(segment_time * 60 * FREQUENCY_DDA);// NB: converts minutes to seconds st_pre.dda_ticks_X_substeps = st_pre.dda_ticks * DDA_SUBSTEPS; // setup motor parameters float correction_steps; for (uint8_t motor=0; motor= 0) { // positive direction st_pre.mot[motor].direction = DIRECTION_CW ^ st_cfg.mot[motor].polarity; st_pre.mot[motor].step_sign = 1; } else { st_pre.mot[motor].direction = DIRECTION_CCW ^ st_cfg.mot[motor].polarity; st_pre.mot[motor].step_sign = -1; } // Detect segment time changes and setup the accumulator correction factor and flag. // Putting this here computes the correct factor even if the motor was dormant for some // number of previous moves. Correction is computed based on the last segment time actually used. if (fabs(segment_time - st_pre.mot[motor].prev_segment_time) > 0.0000001) { // highly tuned FP != compare if (fp_NOT_ZERO(st_pre.mot[motor].prev_segment_time)) { // special case to skip first move st_pre.mot[motor].accumulator_correction_flag = true; st_pre.mot[motor].accumulator_correction = segment_time / st_pre.mot[motor].prev_segment_time; } st_pre.mot[motor].prev_segment_time = segment_time; } // 'Nudge' correction strategy. Inject a single, scaled correction value then hold off // NOTE: This clause can be commented out to test for numerical accuracy and accumulating errors if ((--st_pre.mot[motor].correction_holdoff < 0) && (fabs(following_error[motor]) > STEP_CORRECTION_THRESHOLD)) { st_pre.mot[motor].correction_holdoff = STEP_CORRECTION_HOLDOFF; correction_steps = following_error[motor] * STEP_CORRECTION_FACTOR; if (correction_steps > 0) { correction_steps = min3(correction_steps, fabs(travel_steps[motor]), STEP_CORRECTION_MAX); } else { correction_steps = max3(correction_steps, -fabs(travel_steps[motor]), -STEP_CORRECTION_MAX); } st_pre.mot[motor].corrected_steps += correction_steps; travel_steps[motor] -= correction_steps; } // Compute substeb increment. The accumulator must be *exactly* the incoming // fractional steps times the substep multiplier or positional drift will occur. // Rounding is performed to eliminate a negative bias in the uint32 conversion // that results in long-term negative drift. (fabs/round order doesn't matter) st_pre.mot[motor].substep_increment = round(fabs(travel_steps[motor] * DDA_SUBSTEPS)); } st_pre.block_type = BLOCK_TYPE_ALINE; st_pre.buffer_state = PREP_BUFFER_OWNED_BY_LOADER; // signal that prep buffer is ready stepper_debug("👍🏻"); return (STAT_OK); } /* * st_prep_null() - Keeps the loader happy. Otherwise performs no action */ void st_prep_null() { st_pre.block_type = BLOCK_TYPE_NULL; st_pre.buffer_state = PREP_BUFFER_OWNED_BY_EXEC; // signal that prep buffer is empty } /* * st_prep_command() - Stage command to execution */ void st_prep_command(void *bf) { st_pre.block_type = BLOCK_TYPE_COMMAND; st_pre.bf = (mpBuf_t *)bf; st_pre.buffer_state = PREP_BUFFER_OWNED_BY_LOADER; // signal that prep buffer is ready } /* * st_prep_dwell() - Add a dwell to the move buffer */ void st_prep_dwell(float milliseconds) { st_pre.block_type = BLOCK_TYPE_DWELL; // we need dwell_ticks to be at least 1 st_pre.dwell_ticks = std::max((uint32_t)((milliseconds/1000.0) * FREQUENCY_DWELL), 1UL); st_pre.buffer_state = PREP_BUFFER_OWNED_BY_LOADER; // signal that prep buffer is ready } /* * st_request_out_of_band_dwell() * (only usable while exec isn't running, e.g. in feedhold or stopped states...) * add a dwell to the loader without going through the planner buffers */ void st_request_out_of_band_dwell(float milliseconds) { st_prep_dwell(milliseconds); st_pre.buffer_state = PREP_BUFFER_OWNED_BY_LOADER; // signal that prep buffer is ready st_request_load_move(); } /* * _set_hw_microsteps() - set microsteps in hardware */ static void _set_hw_microsteps(const uint8_t motor, const uint8_t microsteps) { if (motor >= MOTORS) {return;} Motors[motor]->setMicrosteps(microsteps); } /*********************************************************************************** * CONFIGURATION AND INTERFACE FUNCTIONS * Functions to get and set variables from the cfgArray table ***********************************************************************************/ /* HELPERS * _get_motor() - helper to return motor number as an index or -1 if na */ static int8_t _get_motor(const index_t index) { char *ptr; char motors[] = {"123456"}; char tmp[GROUP_LEN+1]; strcpy(tmp, cfgArray[index].group); if ((ptr = strchr(motors, tmp[0])) == NULL) { return (-1); } return (ptr - motors); } /* * _set_motor_steps_per_unit() - what it says * This function will need to be rethought if microstep morphing is implemented */ static void _set_motor_steps_per_unit(nvObj_t *nv) { uint8_t m = _get_motor(nv->index); st_cfg.mot[m].units_per_step = (st_cfg.mot[m].travel_rev * st_cfg.mot[m].step_angle) / (360 * st_cfg.mot[m].microsteps); st_cfg.mot[m].steps_per_unit = 1/st_cfg.mot[m].units_per_step; } /* PER-MOTOR FUNCTIONS * st_set_ma() - map motor to axis * st_set_sa() - set motor step angle * st_set_tr() - set travel per motor revolution * st_set_mi() - set motor microsteps * st_set_pm() - set motor power mode * st_get_pm() - get motor power mode * st_set_pl() - set motor power level */ stat_t st_set_ma(nvObj_t *nv) // map motor to axis { if (nv->value < 0) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_LESS_THAN_MIN_VALUE); } if (nv->value >= AXES) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_EXCEEDS_MAX_VALUE); } set_ui8(nv); return(STAT_OK); } stat_t st_set_sa(nvObj_t *nv) // motor step angle { if (nv->value <= 0) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_LESS_THAN_MIN_VALUE); } if (nv->value >= 360) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_EXCEEDS_MAX_VALUE); } set_flt(nv); _set_motor_steps_per_unit(nv); return(STAT_OK); } stat_t st_set_tr(nvObj_t *nv) // motor travel per revolution { if (nv->value <= 0) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_LESS_THAN_MIN_VALUE); } set_flu(nv); _set_motor_steps_per_unit(nv); return(STAT_OK); } stat_t st_set_mi(nvObj_t *nv) // motor microsteps { if (nv->value <= 0) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_LESS_THAN_MIN_VALUE); } uint8_t mi = (uint8_t)nv->value; if ((mi != 1) && (mi != 2) && (mi != 4) && (mi != 8) && (mi != 16) && (mi != 32)) { nv_add_conditional_message((const char *)"*** WARNING *** Setting non-standard microstep value"); } set_ui8(nv); // set it anyway, even if it's unsupported _set_motor_steps_per_unit(nv); _set_hw_microsteps(_get_motor(nv->index), (uint8_t)nv->value); return (STAT_OK); } stat_t st_get_su(nvObj_t *nv) // motor steps per unit (direct) { uint8_t m = _get_motor(nv->index); nv->value = st_cfg.mot[m].steps_per_unit; nv->valuetype = TYPE_FLOAT; nv->precision = cfgArray[nv->index].precision; return(STAT_OK); } stat_t st_set_su(nvObj_t *nv) // motor steps per unit (direct) { // Don't set a zero or negative value - just calculate based on sa, tr, and mi // This way, if STEPS_PER_UNIT is set to 0 it is unused and we get the computed value uint8_t m = _get_motor(nv->index); if(nv->value <= 0) { nv->value = st_cfg.mot[m].steps_per_unit; _set_motor_steps_per_unit(nv); return(STAT_OK); } // Do unit conversion here because it's a reciprocal value (rather than process_incoming_float()) if (cm_get_units_mode(MODEL) == INCHES) { if (cm_get_axis_type(nv->index) == AXIS_TYPE_LINEAR) { nv->value *= INCHES_PER_MM; } } set_flt(nv); st_cfg.mot[m].units_per_step = 1.0/st_cfg.mot[m].steps_per_unit; // Scale TR so all the other values make sense // You could scale any one of the other values, but TR makes the most sense st_cfg.mot[m].travel_rev = (360.0*st_cfg.mot[m].microsteps)/(st_cfg.mot[m].steps_per_unit*st_cfg.mot[m].step_angle); return(STAT_OK); } stat_t st_set_pm(nvObj_t *nv) // set motor power mode { if (nv->value < 0) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_LESS_THAN_MIN_VALUE); } if (nv->value >= MOTOR_POWER_MODE_MAX_VALUE) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_EXCEEDS_MAX_VALUE); } uint8_t motor = _get_motor(nv->index); if (motor > MOTORS) { nv->valuetype = TYPE_NULL; return STAT_INPUT_VALUE_RANGE_ERROR; }; // We do this *here* in order for this to take effect immediately. // setPowerMode() sets the value and also executes it. Motors[motor]->setPowerMode((stPowerMode)nv->value); return (STAT_OK); } stat_t st_get_pm(nvObj_t *nv) // get motor power mode { uint8_t motor = _get_motor(nv->index); if (motor > MOTORS) { nv->valuetype = TYPE_NULL; return STAT_INPUT_VALUE_RANGE_ERROR; }; nv->value = (float)Motors[motor]->getPowerMode(); nv->valuetype = TYPE_INT; return (STAT_OK); } /* * st_set_pl() - set motor power level * * Input value may vary from 0.000 to 1.000 The setting is scaled to allowable PWM range. * This function sets both the scaled and dynamic power levels, and applies the * scaled value to the vref. */ stat_t st_set_pl(nvObj_t *nv) // motor power level { if (nv->value < (float)0.0) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_LESS_THAN_MIN_VALUE); } if (nv->value > (float)1.0) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_EXCEEDS_MAX_VALUE); } set_flt(nv); // set power_setting value in the motor config struct (st) uint8_t motor = _get_motor(nv->index); st_cfg.mot[motor].power_level = nv->value; st_run.mot[motor].power_level_dynamic = (st_cfg.mot[motor].power_level); Motors[motor]->setPowerLevel(st_cfg.mot[motor].power_level); return(STAT_OK); } /* * st_get_pwr() - get current motor power * * Returns the current power level of the motor given it's enable/disable state * Returns 0.0 if motor is de-energized or disabled * Can be extended to report idle setback by changing getCurrentPowerLevel() */ stat_t st_get_pwr(nvObj_t *nv) { // this is kind of a hack to extract the motor number from the table uint8_t motor = (cfgArray[nv->index].token[3] & 0x0F) - 1; if (motor > MOTORS) { return STAT_INPUT_VALUE_RANGE_ERROR; }; nv->value = Motors[motor]->getCurrentPowerLevel(motor); nv->valuetype = TYPE_FLOAT; nv->precision = cfgArray[nv->index].precision; return (STAT_OK); } /* GLOBAL FUNCTIONS (SYSTEM LEVEL) * * st_set_mt() - set global motor timeout in seconds * st_set_me() - enable motor power * st_set_md() - disable motor power * * Calling me or md with NULL will enable or disable all motors * Setting a value of 0 will enable or disable all motors * Setting a value from 1 to MOTORS will enable or disable that motor only */ stat_t st_set_mt(nvObj_t *nv) { if (nv->value < MOTOR_TIMEOUT_SECONDS_MIN) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_LESS_THAN_MIN_VALUE); } if (nv->value > MOTOR_TIMEOUT_SECONDS_MAX) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_EXCEEDS_MAX_VALUE); } st_cfg.motor_power_timeout = nv->value; return (STAT_OK); } // Make sure this function is not part of initialization --> f00 // nv->value is seconds of timeout stat_t st_set_me(nvObj_t *nv) { for (uint8_t motor = MOTOR_1; motor < MOTORS; motor++) { Motors[motor]->enable(nv->value); // nv->value is the timeout or 0 for default } return (STAT_OK); } // Make sure this function is not part of initialization --> f00 // nv-value is motor to disable, or 0 for all motors stat_t st_set_md(nvObj_t *nv) { if (nv->value < 0) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_LESS_THAN_MIN_VALUE); } if (nv->value > MOTORS) { nv->valuetype = TYPE_NULL; return (STAT_INPUT_EXCEEDS_MAX_VALUE); } // de-energize all motors if ((uint8_t)nv->value == 0) { // 0 means all motors for (uint8_t motor = MOTOR_1; motor < MOTORS; motor++) { Motors[motor]->disable(); } } else { // otherwise it's just one motor Motors[(uint8_t)nv->value -1]->disable(); } return (STAT_OK); } /*********************************************************************************** * TEXT MODE SUPPORT * Functions to print variables from the cfgArray table ***********************************************************************************/ #ifdef __TEXT_MODE static const char msg_units0[] = " in"; // used by generic print functions static const char msg_units1[] = " mm"; static const char msg_units2[] = " deg"; static const char *const msg_units[] = { msg_units0, msg_units1, msg_units2 }; #define DEGREE_INDEX 2 static const char fmt_me[] = "motors energized\n"; static const char fmt_md[] = "motors de-energized\n"; static const char fmt_mt[] = "[mt] motor idle timeout%14.2f seconds\n"; static const char fmt_0ma[] = "[%s%s] m%s map to axis%15d [0=X,1=Y,2=Z...]\n"; static const char fmt_0sa[] = "[%s%s] m%s step angle%20.3f%s\n"; static const char fmt_0tr[] = "[%s%s] m%s travel per revolution%10.4f%s\n"; static const char fmt_0mi[] = "[%s%s] m%s microsteps%16d [1,2,4,8,16,32]\n"; static const char fmt_0su[] = "[%s%s] m%s steps per unit %17.5f steps per%s\n"; static const char fmt_0po[] = "[%s%s] m%s polarity%18d [0=normal,1=reverse]\n"; static const char fmt_0pm[] = "[%s%s] m%s power management%10d [0=disabled,1=always on,2=in cycle,3=when moving]\n"; static const char fmt_0pl[] = "[%s%s] m%s motor power level%13.3f [0.000=minimum, 1.000=maximum]\n"; static const char fmt_pwr[] = "[%s%s] Motor %c power level:%12.3f\n"; void st_print_me(nvObj_t *nv) { text_print(nv, fmt_me);} // TYPE_NULL - message only void st_print_md(nvObj_t *nv) { text_print(nv, fmt_md);} // TYPE_NULL - message only void st_print_mt(nvObj_t *nv) { text_print(nv, fmt_mt);} // TYPE_FLOAT static void _print_motor_int(nvObj_t *nv, const char *format) { sprintf(cs.out_buf, format, nv->group, nv->token, nv->group, (int)nv->value); xio_writeline(cs.out_buf); } static void _print_motor_flt(nvObj_t *nv, const char *format) { sprintf(cs.out_buf, format, nv->group, nv->token, nv->group, nv->value); xio_writeline(cs.out_buf); } static void _print_motor_flt_units(nvObj_t *nv, const char *format, uint8_t units) { sprintf(cs.out_buf, format, nv->group, nv->token, nv->group, nv->value, GET_TEXT_ITEM(msg_units, units)); xio_writeline(cs.out_buf); } static void _print_motor_pwr(nvObj_t *nv, const char *format) { sprintf(cs.out_buf, format, nv->group, nv->token, nv->token[0], nv->value); xio_writeline(cs.out_buf); } void st_print_ma(nvObj_t *nv) { _print_motor_int(nv, fmt_0ma);} void st_print_sa(nvObj_t *nv) { _print_motor_flt_units(nv, fmt_0sa, DEGREE_INDEX);} void st_print_tr(nvObj_t *nv) { _print_motor_flt_units(nv, fmt_0tr, cm_get_units_mode(MODEL));} void st_print_mi(nvObj_t *nv) { _print_motor_int(nv, fmt_0mi);} void st_print_su(nvObj_t *nv) { _print_motor_flt_units(nv, fmt_0su, cm_get_units_mode(MODEL));} void st_print_po(nvObj_t *nv) { _print_motor_int(nv, fmt_0po);} void st_print_pm(nvObj_t *nv) { _print_motor_int(nv, fmt_0pm);} void st_print_pl(nvObj_t *nv) { _print_motor_flt(nv, fmt_0pl);} void st_print_pwr(nvObj_t *nv){ _print_motor_pwr(nv, fmt_pwr);} #endif // __TEXT_MODE