1
0
mirror of https://github.com/bdring/Grbl_Esp32.git synced 2025-08-29 17:19:50 +02:00

Cleaned up AMASS code (#635)

* Cleaned up AMASS code

More #defines gone
74 lines shorter

Tested by comparing the result of original AMASS computation code to
the new code with values surrounding all of the cutoff frequencies.

* I2SOut tick calculation

* Sorted out units for stepper pulse periods

I tried to make it clear what the units are
at different places in the code, and to use
argument datatypes that clearly show the
value range at different points, instead of
relying on implicit type promotion.  Hopefully
this will make it easier to understand when,
where, and why unit conversions occur.

* Update Stepper.h

* Deleted AMASS Config.h option

... as it is no longer optional
This commit is contained in:
Mitch Bradley
2020-10-16 12:50:31 -10:00
committed by GitHub
parent 01aa028043
commit ab36c5a86d
7 changed files with 50 additions and 95 deletions

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View File

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