diff --git a/Grbl_Esp32/src/Config.h b/Grbl_Esp32/src/Config.h index 2de0ac50..658cade3 100644 --- a/Grbl_Esp32/src/Config.h +++ b/Grbl_Esp32/src/Config.h @@ -369,13 +369,6 @@ const int REPORT_WCO_REFRESH_IDLE_COUNT = 10; // (2-255) Must be less than or e // certain the step segment buffer is increased/decreased to account for these changes. const int ACCELERATION_TICKS_PER_SECOND = 100; -// Adaptive Multi-Axis Step Smoothing (AMASS) is an advanced feature that does what its name implies, -// smoothing the stepping of multi-axis motions. This feature smooths motion particularly at low step -// frequencies below 10kHz, where the aliasing between axes of multi-axis motions can cause audible -// noise and shake your machine. At even lower step frequencies, AMASS adapts and provides even better -// step smoothing. See Stepper.c for more details on the AMASS system works. -#define ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING // Default enabled. Comment to disable. - // Sets the maximum step rate allowed to be written as a Grbl setting. This option enables an error // check in the settings module to prevent settings values that will exceed this limitation. The maximum // step rate is strictly limited by the CPU speed and will change if something other than an AVR running diff --git a/Grbl_Esp32/src/I2SOut.cpp b/Grbl_Esp32/src/I2SOut.cpp index f6b261b5..471731d0 100644 --- a/Grbl_Esp32/src/I2SOut.cpp +++ b/Grbl_Esp32/src/I2SOut.cpp @@ -116,8 +116,8 @@ static portMUX_TYPE i2s_out_spinlock = portMUX_INITIALIZER_UNLOCKED; static int i2s_out_initialized = 0; # ifdef USE_I2S_OUT_STREAM_IMPL -static volatile uint64_t i2s_out_pulse_period; -static uint64_t i2s_out_remain_time_until_next_pulse; // Time remaining until the next pulse (μsec) +static volatile uint32_t i2s_out_pulse_period; +static uint32_t i2s_out_remain_time_until_next_pulse; // Time remaining until the next pulse (μsec) static volatile i2s_out_pulse_func_t i2s_out_pulse_func; # endif @@ -657,10 +657,9 @@ int IRAM_ATTR i2s_out_set_stepping() { return 0; } -int IRAM_ATTR i2s_out_set_pulse_period(uint64_t period) { +int IRAM_ATTR i2s_out_set_pulse_period(uint32_t period) { # ifdef USE_I2S_OUT_STREAM_IMPL - // Use 64-bit values to avoid overflowing during the calculation. - i2s_out_pulse_period = period * 1000000 / F_STEPPER_TIMER; + i2s_out_pulse_period = period; # endif return 0; } diff --git a/Grbl_Esp32/src/I2SOut.h b/Grbl_Esp32/src/I2SOut.h index f672a278..5ae31c16 100644 --- a/Grbl_Esp32/src/I2SOut.h +++ b/Grbl_Esp32/src/I2SOut.h @@ -156,10 +156,9 @@ int i2s_out_set_stepping(); void i2s_out_delay(); /* - Set the pulse callback period in ISR ticks. - (same value of the timer period for the ISR) + Set the pulse callback period in microseconds */ -int i2s_out_set_pulse_period(uint64_t period); +int i2s_out_set_pulse_period(uint32_t usec); /* Register a callback function to generate pulse data diff --git a/Grbl_Esp32/src/MachineCommon.h b/Grbl_Esp32/src/MachineCommon.h index 627da376..95cea998 100644 --- a/Grbl_Esp32/src/MachineCommon.h +++ b/Grbl_Esp32/src/MachineCommon.h @@ -15,8 +15,7 @@ #endif // ESP32 CPU Settings -const int32_t F_TIMERS = 80000000; // a reference to the speed of ESP32 timers -#define F_STEPPER_TIMER 20000000 // frequency of step pulse timer +const uint32_t fTimers = 80000000; // a reference to the speed of ESP32 timers // =============== Don't change or comment these out ====================== // They are for legacy purposes and will not affect your I/O diff --git a/Grbl_Esp32/src/NutsBolts.h b/Grbl_Esp32/src/NutsBolts.h index 5d6b0fb7..185be0a7 100644 --- a/Grbl_Esp32/src/NutsBolts.h +++ b/Grbl_Esp32/src/NutsBolts.h @@ -61,7 +61,6 @@ static inline int toMotor2(int axis) { // Conversions const double MM_PER_INCH = (25.40); const double INCH_PER_MM = (0.0393701); -#define TICKS_PER_MICROSECOND (F_STEPPER_TIMER / 1000000) // Different from AVR version const int DELAY_MODE_DWELL = 0; const int DELAY_MODE_SYS_SUSPEND = 1; diff --git a/Grbl_Esp32/src/Stepper.cpp b/Grbl_Esp32/src/Stepper.cpp index 8fa21906..82c4a921 100644 --- a/Grbl_Esp32/src/Stepper.cpp +++ b/Grbl_Esp32/src/Stepper.cpp @@ -45,14 +45,10 @@ static st_block_t st_block_buffer[SEGMENT_BUFFER_SIZE - 1]; // the planner, where the remaining planner block steps still can. typedef struct { uint16_t n_step; // Number of step events to be executed for this segment - uint16_t cycles_per_tick; // Step distance traveled per ISR tick, aka step rate. + uint16_t isrPeriod; // Time to next ISR tick, in units of timer ticks uint8_t st_block_index; // Stepper block data index. Uses this information to execute this segment. -#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING - uint8_t amass_level; // Indicates AMASS level for the ISR to execute this segment -#else - uint8_t prescaler; // Without AMASS, a prescaler is required to adjust for slow timing. -#endif - uint16_t spindle_rpm; // TODO get rid of this. + uint8_t amass_level; // AMASS level for the ISR to execute this segment + uint16_t spindle_rpm; // TODO get rid of this. } segment_t; static segment_t segment_buffer[SEGMENT_BUFFER_SIZE]; @@ -70,9 +66,7 @@ typedef struct { uint8_t step_pulse_time; // Step pulse reset time after step rise uint8_t step_outbits; // The next stepping-bits to be output uint8_t dir_outbits; -#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING uint32_t steps[MAX_N_AXIS]; -#endif uint16_t step_count; // Steps remaining in line segment motion uint8_t exec_block_index; // Tracks the current st_block index. Change indicates new block. @@ -94,7 +88,7 @@ static volatile uint8_t busy; static plan_block_t* pl_block; // Pointer to the planner block being prepped static st_block_t* st_prep_block; // Pointer to the stepper block data being prepped -// esp32 work around for diable in main loop +// esp32 work around for disable in main loop uint64_t stepper_idle_counter; // used to count down until time to disable stepper drivers bool stepper_idle; @@ -243,7 +237,7 @@ static void stepper_pulse_func() { // Initialize new step segment and load number of steps to execute st.exec_segment = &segment_buffer[segment_buffer_tail]; // Initialize step segment timing per step and load number of steps to execute. - Stepper_Timer_WritePeriod(st.exec_segment->cycles_per_tick); + Stepper_Timer_WritePeriod(st.exec_segment->isrPeriod); st.step_count = st.exec_segment->n_step; // NOTE: Can sometimes be zero when moving slow. // If the new segment starts a new planner block, initialize stepper variables and counters. // NOTE: When the segment data index changes, this indicates a new planner block. @@ -258,12 +252,10 @@ static void stepper_pulse_func() { // TODO ABC } st.dir_outbits = st.exec_block->direction_bits; -#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING - // With AMASS enabled, adjust Bresenham axis increment counters according to AMASS level. + // Adjust Bresenham axis increment counters according to AMASS level. for (int axis = 0; axis < n_axis; axis++) { st.steps[axis] = st.exec_block->steps[axis] >> st.exec_segment->amass_level; } -#endif // Set real-time spindle output as segment is loaded, just prior to the first step. spindle->set_rpm(st.exec_segment->spindle_rpm); } else { @@ -374,7 +366,7 @@ void st_wake_up() { // Step pulse delay handling is not require with ESP32...the RMT function does it. #else // Normal operation // Set step pulse time. Ad hoc computation from oscilloscope. Uses two's complement. - st.step_pulse_time = -(((pulse_microseconds->get() - 2) * TICKS_PER_MICROSECOND) >> 3); + st.step_pulse_time = -(((pulse_microseconds->get() - 2) * ticksPerMicrosecond) >> 3); #endif // Enable Stepper Driver Interrupt Stepper_Timer_Start(); @@ -538,20 +530,15 @@ void st_prep_buffer() { st_prep_block->direction_bits = pl_block->direction_bits; uint8_t idx; auto n_axis = number_axis->get(); -#ifndef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING - for (idx = 0; idx < n_axis; idx++) { - st_prep_block->steps[idx] = pl_block->steps[idx]; - } - st_prep_block->step_event_count = pl_block->step_event_count; -#else - // With AMASS enabled, simply bit-shift multiply all Bresenham data by the max AMASS - // level, such that we never divide beyond the original data anywhere in the algorithm. + + // Bit-shift multiply all Bresenham data by the max AMASS level so that + // we never divide beyond the original data anywhere in the algorithm. // If the original data is divided, we can lose a step from integer roundoff. for (idx = 0; idx < n_axis; idx++) { - st_prep_block->steps[idx] = pl_block->steps[idx] << MAX_AMASS_LEVEL; + st_prep_block->steps[idx] = pl_block->steps[idx] << maxAmassLevel; } - st_prep_block->step_event_count = pl_block->step_event_count << MAX_AMASS_LEVEL; -#endif + st_prep_block->step_event_count = pl_block->step_event_count << maxAmassLevel; + // Initialize segment buffer data for generating the segments. prep.steps_remaining = (float)pl_block->step_event_count; prep.step_per_mm = prep.steps_remaining / pl_block->millimeters; @@ -839,49 +826,28 @@ void st_prep_buffer() { // outputs the exact acceleration and velocity profiles as computed by the planner. dt += prep.dt_remainder; // Apply previous segment partial step execute time + // dt is in minutes so inv_rate is in minutes float inv_rate = dt / (last_n_steps_remaining - step_dist_remaining); // Compute adjusted step rate inverse // Compute CPU cycles per step for the prepped segment. - uint32_t cycles = ceil((TICKS_PER_MICROSECOND * 1000000 * 60) * inv_rate); // (cycles/step) + // fStepperTimer is in units of timerTicks/sec, so the dimensional analysis is + // timerTicks/sec * 60 sec/minute * minutes = timerTicks + uint32_t timerTicks = ceil((fStepperTimer * 60) * inv_rate); // (timerTicks/step) + int level; -#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING // Compute step timing and multi-axis smoothing level. - // NOTE: AMASS overdrives the timer with each level, so only one prescalar is required. - if (cycles < AMASS_LEVEL1) { - prep_segment->amass_level = 0; - } else { - if (cycles < AMASS_LEVEL2) { - prep_segment->amass_level = 1; - } else if (cycles < AMASS_LEVEL3) { - prep_segment->amass_level = 2; - } else { - prep_segment->amass_level = 3; + for (level = 0; level < maxAmassLevel; level++) { + if (timerTicks < amassThreshold) { + break; } - cycles >>= prep_segment->amass_level; - prep_segment->n_step <<= prep_segment->amass_level; + timerTicks >>= 1; } - if (cycles < (1UL << 16)) { - prep_segment->cycles_per_tick = cycles; // < 65536 (4.1ms @ 16MHz) - } else { - prep_segment->cycles_per_tick = 0xffff; // Just set the slowest speed possible. - } -#else - // Compute step timing and timer prescalar for normal step generation. - if (cycles < (1UL << 16)) { // < 65536 (4.1ms @ 16MHz) - prep_segment->prescaler = 1; // prescaler: 0 - prep_segment->cycles_per_tick = cycles; - } else if (cycles < (1UL << 19)) { // < 524288 (32.8ms@16MHz) - prep_segment->prescaler = 2; // prescaler: 8 - prep_segment->cycles_per_tick = cycles >> 3; - } else { - prep_segment->prescaler = 3; // prescaler: 64 - if (cycles < (1UL << 22)) { // < 4194304 (262ms@16MHz) - prep_segment->cycles_per_tick = cycles >> 6; - } else { // Just set the slowest speed possible. (Around 4 step/sec.) - prep_segment->cycles_per_tick = 0xffff; - } - } -#endif + prep_segment->amass_level = level; + prep_segment->n_step <<= level; + // isrPeriod is stored as 16 bits, so limit timerTicks to the + // largest value that will fit in a uint16_t. + prep_segment->isrPeriod = timerTicks > 0xffff ? 0xffff : timerTicks; + // Segment complete! Increment segment buffer indices, so stepper ISR can immediately execute it. segment_buffer_head = segment_next_head; if (++segment_next_head == SEGMENT_BUFFER_SIZE) { @@ -935,21 +901,23 @@ float st_get_realtime_rate() { } } -void IRAM_ATTR Stepper_Timer_WritePeriod(uint64_t alarm_val) { +// The argument is in units of ticks of the timer that generates ISRs +void IRAM_ATTR Stepper_Timer_WritePeriod(uint16_t timerTicks) { if (current_stepper == ST_I2S_STREAM) { #ifdef USE_I2S_STEPS - // 1 tick = F_TIMERS / F_STEPPER_TIMER + // 1 tick = fTimers / fStepperTimer // Pulse ISR is called for each tick of alarm_val. - i2s_out_set_pulse_period(alarm_val); + // The argument to i2s_out_set_pulse_period is in units of microseconds + i2s_out_set_pulse_period(((uint32_t)timerTicks) / ticksPerMicrosecond); #endif } else { - timer_set_alarm_value(STEP_TIMER_GROUP, STEP_TIMER_INDEX, alarm_val); + timer_set_alarm_value(STEP_TIMER_GROUP, STEP_TIMER_INDEX, (uint64_t)timerTicks); } } void IRAM_ATTR Stepper_Timer_Init() { timer_config_t config; - config.divider = F_TIMERS / F_STEPPER_TIMER; + config.divider = fTimers / fStepperTimer; config.counter_dir = TIMER_COUNT_UP; config.counter_en = TIMER_PAUSE; config.alarm_en = TIMER_ALARM_EN; diff --git a/Grbl_Esp32/src/Stepper.h b/Grbl_Esp32/src/Stepper.h index bf5e5267..5bd04482 100644 --- a/Grbl_Esp32/src/Stepper.h +++ b/Grbl_Esp32/src/Stepper.h @@ -46,25 +46,23 @@ struct PrepFlag { uint8_t decelOverride : 1; }; +// fStepperTimer should be an integer divisor of the bus speed, i.e. of fTimers +const uint32_t fStepperTimer = 20000000; // frequency of step pulse timer +const int ticksPerMicrosecond = fStepperTimer / 1000000; + // Define Adaptive Multi-Axis Step-Smoothing(AMASS) levels and cutoff frequencies. The highest level // frequency bin starts at 0Hz and ends at its cutoff frequency. The next lower level frequency bin // starts at the next higher cutoff frequency, and so on. The cutoff frequencies for each level must // be considered carefully against how much it over-drives the stepper ISR, the accuracy of the 16-bit // timer, and the CPU overhead. Level 0 (no AMASS, normal operation) frequency bin starts at the // Level 1 cutoff frequency and up to as fast as the CPU allows (over 30kHz in limited testing). +// For efficient computation, each cutoff frequency is twice the previous one. // NOTE: AMASS cutoff frequency multiplied by ISR overdrive factor must not exceed maximum step frequency. // NOTE: Current settings are set to overdrive the ISR to no more than 16kHz, balancing CPU overhead // and timer accuracy. Do not alter these settings unless you know what you are doing. -///#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING -#define MAX_AMASS_LEVEL 3 -// AMASS_LEVEL0: Normal operation. No AMASS. No upper cutoff frequency. Starts at LEVEL1 cutoff frequency. -// Note ESP32 use F_STEPPER_TIMER rather than the AVR F_CPU -const int AMASS_LEVEL1 = (F_STEPPER_TIMER / 8000); // Over-drives ISR (x2). Defined as F_CPU/(Cutoff frequency in Hz) -const int AMASS_LEVEL2 = (F_STEPPER_TIMER / 4000); // Over-drives ISR (x4) -const int AMASS_LEVEL3 = (F_STEPPER_TIMER / 2000); // Over-drives ISR (x8) -static_assert(MAX_AMASS_LEVEL >= 1, "AMASS must have 1 or more levels to operate correctly."); -//#endif +const uint32_t amassThreshold = fStepperTimer / 8000; +const int maxAmassLevel = 3; // Each level increase doubles the threshold const timer_group_t STEP_TIMER_GROUP = TIMER_GROUP_0; const timer_idx_t STEP_TIMER_INDEX = TIMER_0; @@ -132,7 +130,7 @@ bool get_stepper_disable(); // returns the state of the pin void set_stepper_pins_on(uint8_t onMask); void set_direction_pins_on(uint8_t onMask); -void Stepper_Timer_WritePeriod(uint64_t alarm_val); +void Stepper_Timer_WritePeriod(uint16_t timerTicks); void Stepper_Timer_Init(); void Stepper_Timer_Start(); void Stepper_Timer_Stop();