mirror of
https://github.com/bdring/Grbl_Esp32.git
synced 2025-08-29 09:10:03 +02:00
Big stepper / stepType cleanup
Moved all the hardware dependencies out of Stepper.cpp mostly into the new file Stepping.cpp. stepType is now engine. There is a new section stepping: that holds the shared stepper delay parameters. The parameter name prefixes are now consistently either _us for microseconds or _ms for milliseconds. Nearly all the references to _engine are now encapsulated in Stepping.cpp, except for a couple that are still lurking in StandardStepper.cpp. I haven't figured out the best way to fix those. Ideally, the engine should be auto-selected based on the type of pins you use for step: .
This commit is contained in:
@@ -196,13 +196,10 @@ bool user_defined_homing(AxisMask cycle_mask) {
|
||||
}
|
||||
} while (!switch_touched);
|
||||
|
||||
#ifdef USE_I2S_STEPS
|
||||
if (config->_stepType == ST_I2S_STREAM) {
|
||||
if (!approach) {
|
||||
delay_ms(I2S_OUT_DELAY_MS);
|
||||
}
|
||||
if (!approach) {
|
||||
config->_stepping->synchronize();
|
||||
}
|
||||
#endif
|
||||
|
||||
st_reset(); // Immediately force kill steppers and reset step segment buffer.
|
||||
delay_ms(config->_homing->_debounce); // Delay to allow transient dynamics to dissipate.
|
||||
|
||||
|
@@ -1,11 +1,13 @@
|
||||
name: 6 Pack StepStick BESC
|
||||
board: 6-pack
|
||||
|
||||
idle_time: 250
|
||||
step_type: I2S_static
|
||||
dir_delay_microseconds: 1
|
||||
pulse_microseconds: 2
|
||||
disable_delay_us: 0
|
||||
stepping:
|
||||
engine: I2S_static
|
||||
idle_ms: 250
|
||||
dir_delay_us: 1
|
||||
pulse_us: 2
|
||||
disable_delay_us: 0
|
||||
|
||||
homing_init_lock: false
|
||||
|
||||
i2so:
|
||||
|
@@ -6,6 +6,13 @@ i2so:
|
||||
ws: gpio.17
|
||||
data: gpio.21
|
||||
|
||||
stepping:
|
||||
engine: I2S_STREAM
|
||||
idle_ms: 250
|
||||
pulse_us: 2
|
||||
dir_delay_us: 1
|
||||
disable_delay_us: 0
|
||||
|
||||
axes:
|
||||
number_axis: 3
|
||||
|
||||
@@ -19,13 +26,13 @@ axes:
|
||||
cycle: 1
|
||||
positive_direction: false
|
||||
mpos: 10.000
|
||||
feed_rate: 500.000
|
||||
seek_rate: 100.000
|
||||
debounce: 10
|
||||
pulloff: 1.000
|
||||
square: false
|
||||
search_scaler: 1.100
|
||||
locate_scaler: 5.000
|
||||
seek_rate: 500.000
|
||||
feed_rate: 100.000
|
||||
seek_scaler: 1.100
|
||||
feed_scaler: 1.000
|
||||
|
||||
gang0:
|
||||
endstops:
|
||||
@@ -50,13 +57,13 @@ axes:
|
||||
cycle: 2
|
||||
positive_direction: false
|
||||
mpos: 10.000
|
||||
feed_rate: 500.000
|
||||
seek_rate: 100.000
|
||||
debounce: 10
|
||||
pulloff: 1.000
|
||||
square: false
|
||||
search_scaler: 1.100
|
||||
locate_scaler: 5.000
|
||||
seek_rate: 500.000
|
||||
feed_rate: 100.000
|
||||
seek_scaler: 1.100
|
||||
feed_scaler: 1.000
|
||||
|
||||
gang0:
|
||||
endstops:
|
||||
@@ -80,13 +87,13 @@ axes:
|
||||
cycle: 2
|
||||
positive_direction: false
|
||||
mpos: 10.000
|
||||
feed_rate: 500.000
|
||||
seek_rate: 100.000
|
||||
debounce: 10
|
||||
pulloff: 1.000
|
||||
square: false
|
||||
search_scaler: 1.100
|
||||
locate_scaler: 5.000
|
||||
seek_rate: 500.000
|
||||
feed_rate: 100.000
|
||||
seek_scaler: 1.100
|
||||
feed_scaler: 1.000
|
||||
|
||||
gang0:
|
||||
endstops:
|
||||
@@ -158,14 +165,7 @@ comms:
|
||||
dhcp: true
|
||||
channel: 1
|
||||
|
||||
pulse_microseconds: 2
|
||||
dir_delay_microseconds: 1
|
||||
disable_delay_us: 0
|
||||
idle_time: 250
|
||||
|
||||
|
||||
software_debounce_ms: 0
|
||||
step_type: I2S_STREAM
|
||||
laser_mode: false
|
||||
arc_tolerance: 0.002
|
||||
junction_deviation: 0.010
|
||||
|
@@ -2,11 +2,13 @@ name: "ESP32 Dev Controller V4"
|
||||
board: "ESP32 Dev Controller V4"
|
||||
yaml_wiki: "https://github.com/bdring/Grbl_Esp32/wiki/YAML-Config-File"
|
||||
|
||||
idle_time: 250
|
||||
step_type: RMT
|
||||
dir_delay_microseconds: 1
|
||||
pulse_microseconds: 2
|
||||
disable_delay_us: 0
|
||||
stepping:
|
||||
engine: RMT
|
||||
idle_ms: 250
|
||||
dir_delay_us: 1
|
||||
pulse_us: 2
|
||||
disable_delay_us: 0
|
||||
|
||||
homing_init_lock: false
|
||||
|
||||
axes:
|
||||
@@ -70,9 +72,10 @@ axes:
|
||||
cycle: 1
|
||||
mpos: 10
|
||||
positive_direction: true
|
||||
feed_rate: 30.000
|
||||
seek_rate: 300.000
|
||||
locate_scaler: 2.000
|
||||
feed_rate: 30.000
|
||||
seek_scaler: 2.000
|
||||
feed_scaler: 1.000
|
||||
debounce: 500
|
||||
pulloff: 1.000
|
||||
|
||||
|
@@ -2,11 +2,13 @@ name: "ESP32 Dev Controller V4"
|
||||
board: "ESP32 Dev Controller V4"
|
||||
yaml_wiki: "https://github.com/bdring/Grbl_Esp32/wiki/YAML-Config-File"
|
||||
|
||||
idle_time: 250
|
||||
step_type: rmt
|
||||
dir_delay_microseconds: 1
|
||||
pulse_microseconds: 2
|
||||
disable_delay_us: 0
|
||||
stepping:
|
||||
engine: rmt
|
||||
idle_ms: 250
|
||||
dir_delay_us: 1
|
||||
pulse_us: 2
|
||||
disable_delay_us: 0
|
||||
|
||||
homing_init_lock: false
|
||||
|
||||
axes:
|
||||
|
@@ -49,6 +49,7 @@
|
||||
#include "Settings.h"
|
||||
#include "SettingsDefinitions.h"
|
||||
#include "Machine/MachineConfig.h"
|
||||
#include "Stepper.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <driver/periph_ctrl.h>
|
||||
@@ -124,9 +125,8 @@ static portMUX_TYPE i2s_out_spinlock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static int i2s_out_initialized = 0;
|
||||
|
||||
#ifdef USE_I2S_OUT_STREAM_IMPL
|
||||
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;
|
||||
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)
|
||||
#endif
|
||||
|
||||
static uint8_t i2s_out_ws_pin = 255;
|
||||
@@ -374,29 +374,28 @@ static int IRAM_ATTR i2s_fillout_dma_buffer(lldesc_t* dma_desc) {
|
||||
// pulser status may change in pulse phase func, so I need to check it every time.
|
||||
if (i2s_out_pulser_status == STEPPING) {
|
||||
// fillout future DMA buffer (tail of the DMA buffer chains)
|
||||
if (i2s_out_pulse_func != NULL) {
|
||||
uint32_t old_rw_pos = o_dma.rw_pos;
|
||||
I2S_OUT_PULSER_EXIT_CRITICAL(); // Temporarily unlocked status lock as it may be locked in pulse callback.
|
||||
(*i2s_out_pulse_func)(); // should be pushed into buffer max DMA_SAMPLE_SAFE_COUNT
|
||||
I2S_OUT_PULSER_ENTER_CRITICAL(); // Lock again.
|
||||
// Calculate pulse period.
|
||||
i2s_out_remain_time_until_next_pulse += i2s_out_pulse_period - I2S_OUT_USEC_PER_PULSE * (o_dma.rw_pos - old_rw_pos);
|
||||
if (i2s_out_pulser_status == WAITING) {
|
||||
// i2s_out_set_passthrough() has called from the pulse function.
|
||||
// It needs to go into pass-through mode.
|
||||
// This DMA descriptor must be a tail of the chain.
|
||||
dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer.
|
||||
} else if (i2s_out_pulser_status == PASSTHROUGH) {
|
||||
// i2s_out_reset() has called during the execution of the pulse function.
|
||||
// I2S has already in static mode, and buffers has cleared to zero.
|
||||
// To prevent the pulse function from being called back,
|
||||
// we assume that the buffer is already full.
|
||||
i2s_out_remain_time_until_next_pulse = 0; // There is no need to fill the current buffer.
|
||||
o_dma.rw_pos = DMA_SAMPLE_COUNT; // The buffer is full.
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
uint32_t old_rw_pos = o_dma.rw_pos;
|
||||
I2S_OUT_PULSER_EXIT_CRITICAL(); // Temporarily unlocked status lock as it may be locked in pulse callback.
|
||||
Stepper::pulse_func();
|
||||
|
||||
I2S_OUT_PULSER_ENTER_CRITICAL(); // Lock again.
|
||||
// Calculate pulse period.
|
||||
i2s_out_remain_time_until_next_pulse += i2s_out_pulse_period - I2S_OUT_USEC_PER_PULSE * (o_dma.rw_pos - old_rw_pos);
|
||||
if (i2s_out_pulser_status == WAITING) {
|
||||
// i2s_out_set_passthrough() has called from the pulse function.
|
||||
// It needs to go into pass-through mode.
|
||||
// This DMA descriptor must be a tail of the chain.
|
||||
dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer.
|
||||
} else if (i2s_out_pulser_status == PASSTHROUGH) {
|
||||
// i2s_out_reset() has called during the execution of the pulse function.
|
||||
// I2S has already in static mode, and buffers has cleared to zero.
|
||||
// To prevent the pulse function from being called back,
|
||||
// we assume that the buffer is already full.
|
||||
i2s_out_remain_time_until_next_pulse = 0; // There is no need to fill the current buffer.
|
||||
o_dma.rw_pos = DMA_SAMPLE_COUNT; // The buffer is full.
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// no pulse data in push buffer (pulse off or idle or callback is not defined)
|
||||
@@ -678,13 +677,6 @@ int IRAM_ATTR i2s_out_set_pulse_period(uint32_t period) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IRAM_ATTR i2s_out_set_pulse_callback(i2s_out_pulse_func_t func) {
|
||||
#ifdef USE_I2S_OUT_STREAM_IMPL
|
||||
i2s_out_pulse_func = func;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IRAM_ATTR i2s_out_reset() {
|
||||
I2S_OUT_PULSER_ENTER_CRITICAL();
|
||||
i2s_out_stop();
|
||||
@@ -705,7 +697,7 @@ int IRAM_ATTR i2s_out_reset() {
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize funtion (external function)
|
||||
// Initialize function (external function)
|
||||
//
|
||||
// XXX does this really need IRAM_ATTR ? It is not called from an ISR
|
||||
int IRAM_ATTR i2s_out_init(i2s_out_init_t& init_param) {
|
||||
@@ -917,7 +909,6 @@ int IRAM_ATTR i2s_out_init(i2s_out_init_t& init_param) {
|
||||
|
||||
// default pulse callback period (μsec)
|
||||
i2s_out_pulse_period = init_param.pulse_period;
|
||||
i2s_out_pulse_func = init_param.pulse_func;
|
||||
|
||||
// Create the task that will feed the buffer
|
||||
xTaskCreatePinnedToCore(i2sOutTask,
|
||||
@@ -979,7 +970,6 @@ int IRAM_ATTR i2s_out_init() {
|
||||
default_param.ws_pin = wsPin.getNative(Pin::Capabilities::Output | Pin::Capabilities::Native);
|
||||
default_param.bck_pin = bckPin.getNative(Pin::Capabilities::Output | Pin::Capabilities::Native);
|
||||
default_param.data_pin = dataPin.getNative(Pin::Capabilities::Output | Pin::Capabilities::Native);
|
||||
default_param.pulse_func = NULL;
|
||||
default_param.pulse_period = I2S_OUT_USEC_PER_PULSE;
|
||||
default_param.init_val = I2S_OUT_INIT_VAL;
|
||||
|
||||
|
@@ -64,8 +64,6 @@ const int I2S_OUT_DMABUF_LEN = 2000; /* maximum size in bytes (4092 is DMA's l
|
||||
const int I2S_OUT_DELAY_DMABUF_MS = (I2S_OUT_DMABUF_LEN / sizeof(uint32_t) * I2S_OUT_USEC_PER_PULSE / 1000);
|
||||
const int I2S_OUT_DELAY_MS = (I2S_OUT_DELAY_DMABUF_MS * (I2S_OUT_DMABUF_COUNT + 1));
|
||||
|
||||
typedef void (*i2s_out_pulse_func_t)(void);
|
||||
|
||||
typedef struct {
|
||||
/*
|
||||
I2S bitstream (32-bits): Transfers from MSB(bit31) to LSB(bit0) in sequence
|
||||
@@ -80,12 +78,11 @@ typedef struct {
|
||||
If I2S_OUT_PIN_BASE is set to 128,
|
||||
bit0:Expanded GPIO 128, 1: Expanded GPIO 129, ..., v: Expanded GPIO 159
|
||||
*/
|
||||
uint8_t ws_pin;
|
||||
uint8_t bck_pin;
|
||||
uint8_t data_pin;
|
||||
i2s_out_pulse_func_t pulse_func;
|
||||
uint32_t pulse_period; // aka step rate.
|
||||
uint32_t init_val;
|
||||
uint8_t ws_pin;
|
||||
uint8_t bck_pin;
|
||||
uint8_t data_pin;
|
||||
uint32_t pulse_period; // aka step rate.
|
||||
uint32_t init_val;
|
||||
} i2s_out_init_t;
|
||||
|
||||
/*
|
||||
@@ -100,7 +97,6 @@ int i2s_out_init(i2s_out_init_t& init_param);
|
||||
.ws_pin = I2S_OUT_WS,
|
||||
.bck_pin = I2S_OUT_BCK,
|
||||
.data_pin = I2S_OUT_DATA,
|
||||
.pulse_func = NULL,
|
||||
.pulse_period = I2S_OUT_USEC_PER_PULSE,
|
||||
.init_val = I2S_OUT_INIT_VAL,
|
||||
};
|
||||
@@ -160,11 +156,6 @@ void i2s_out_delay();
|
||||
*/
|
||||
int i2s_out_set_pulse_period(uint32_t usec);
|
||||
|
||||
/*
|
||||
Register a callback function to generate pulse data
|
||||
*/
|
||||
int i2s_out_set_pulse_callback(i2s_out_pulse_func_t func);
|
||||
|
||||
/*
|
||||
Get current pulser mode
|
||||
*/
|
||||
|
@@ -260,10 +260,8 @@ static void limits_go_home(uint8_t cycle_mask, uint32_t n_locate_cycles) {
|
||||
// Keep trying until all axes have finished
|
||||
} while (axislock);
|
||||
|
||||
if (config->_stepType == ST_I2S_STREAM) {
|
||||
if (!approach) {
|
||||
delay_ms(I2S_OUT_DELAY_MS);
|
||||
}
|
||||
if (!approach) {
|
||||
config->_stepping->synchronize();
|
||||
}
|
||||
|
||||
Stepper::reset(); // Immediately force kill steppers and reset step segment buffer.
|
||||
|
@@ -6,7 +6,6 @@
|
||||
#include "../NutsBolts.h"
|
||||
#include "../MotionControl.h"
|
||||
#include "../Stepper.h" // stepper_id_t
|
||||
#include "../I2SOut.h" // i2s_out_push_sample
|
||||
#include "MachineConfig.h" // config->
|
||||
|
||||
namespace Machine {
|
||||
@@ -17,7 +16,7 @@ namespace Machine {
|
||||
}
|
||||
|
||||
void Axes::init() {
|
||||
info_serial("Init Motors");
|
||||
info_serial("Axis count %d", config->_axes->_numberAxis);
|
||||
|
||||
if (_sharedStepperDisable.defined()) {
|
||||
_sharedStepperDisable.setAttr(Pin::Attr::Output);
|
||||
@@ -130,34 +129,11 @@ namespace Machine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto wait_direction = config->_directionDelayMicroSeconds;
|
||||
if (wait_direction > 0) {
|
||||
// stepping->turnaround(wait_direction);
|
||||
// Stepper drivers need some time between changing direction and doing a pulse.
|
||||
switch (config->_stepType) {
|
||||
case stepper_id_t::ST_I2S_STREAM:
|
||||
i2s_out_push_sample(wait_direction);
|
||||
break;
|
||||
case stepper_id_t::ST_I2S_STATIC:
|
||||
case stepper_id_t::ST_TIMED: {
|
||||
// wait for step pulse time to complete...some time expired during code above
|
||||
//
|
||||
// If we are using GPIO stepping as opposed to RMT, record the
|
||||
// time that we turned on the direction pins so we can delay a bit.
|
||||
// If we are using RMT, we can't delay here.
|
||||
auto direction_pulse_start_time = esp_timer_get_time() + wait_direction;
|
||||
while ((esp_timer_get_time() - direction_pulse_start_time) < 0) {
|
||||
NOP(); // spin here until time to turn off step
|
||||
}
|
||||
break;
|
||||
}
|
||||
case stepper_id_t::ST_RMT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
config->_stepping->waitDirection();
|
||||
}
|
||||
|
||||
config->_stepping->startPulseTimer();
|
||||
|
||||
// Turn on step pulses for motors that are supposed to step now
|
||||
for (uint8_t axis = X_AXIS; axis < n_axis; axis++) {
|
||||
if (bitnum_istrue(step_mask, axis)) {
|
||||
@@ -172,8 +148,10 @@ namespace Machine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Turn all stepper pins off
|
||||
void IRAM_ATTR Axes::unstep() {
|
||||
config->_stepping->waitPulse();
|
||||
auto n_axis = _numberAxis;
|
||||
for (uint8_t axis = X_AXIS; axis < n_axis; axis++) {
|
||||
for (uint8_t gang_index = 0; gang_index < Axis::MAX_NUMBER_GANGED; gang_index++) {
|
||||
@@ -214,35 +192,6 @@ namespace Machine {
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
// Wait for motion to complete; the axes can still be moving
|
||||
// after the data has been sent to the stepping engine, due
|
||||
// to queuing delays.
|
||||
void Axes::synchronize() {
|
||||
if (config->_stepType == ST_I2S_STREAM) {
|
||||
// XXX instead of a delay, we could sense when the DMA and
|
||||
// FIFO have drained. It might be as simple as waiting for
|
||||
// I2SO.conf1.tx_start == 0, while yielding.
|
||||
delay_ms(I2S_OUT_DELAY_MS);
|
||||
}
|
||||
}
|
||||
void Axes::beginLowLatency() {
|
||||
_switchedStepper = config->_stepType == ST_I2S_STREAM;
|
||||
if (_switchedStepper) {
|
||||
config->_stepType = ST_I2S_STATIC;
|
||||
}
|
||||
}
|
||||
void Axes::endLowLatency() {
|
||||
if (_switchedStepper) {
|
||||
if (i2s_out_get_pulser_status() != PASSTHROUGH) {
|
||||
// Called during streaming. Stop streaming.
|
||||
debug_serial("Stop the I2S streaming and switch to the passthrough mode.");
|
||||
i2s_out_set_passthrough();
|
||||
i2s_out_delay(); // Wait for a change in mode.
|
||||
}
|
||||
config->_stepType = ST_I2S_STREAM;
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration helpers:
|
||||
|
||||
void Axes::group(Configuration::HandlerBase& handler) {
|
||||
|
@@ -67,10 +67,6 @@ namespace Machine {
|
||||
return false;
|
||||
}
|
||||
|
||||
void synchronize(); // Wait for motion to complete
|
||||
void beginLowLatency();
|
||||
void endLowLatency();
|
||||
|
||||
// These are used for setup and to talk to the motors as a group.
|
||||
void init();
|
||||
void read_settings(); // more like 'after read settings, before init'. Oh well...
|
||||
|
@@ -47,6 +47,7 @@ namespace Machine {
|
||||
handler.item("board", _board);
|
||||
handler.item("name", _name);
|
||||
|
||||
handler.section("stepping", _stepping);
|
||||
handler.section("axes", _axes);
|
||||
handler.section("i2so", _i2so);
|
||||
handler.section("spi", _spi);
|
||||
@@ -56,15 +57,9 @@ namespace Machine {
|
||||
handler.section("comms", _comms);
|
||||
handler.section("macros", _macros);
|
||||
|
||||
handler.item("pulse_microseconds", _pulseMicroSeconds);
|
||||
handler.item("dir_delay_microseconds", _directionDelayMicroSeconds);
|
||||
handler.item("disable_delay_us", _disableDelayMicroSeconds);
|
||||
handler.item("idle_time", _idleTime);
|
||||
handler.section("user_outputs", _userOutputs);
|
||||
handler.section("sdcard", _sdCard);
|
||||
handler.item("software_debounce_ms", _softwareDebounceMs);
|
||||
handler.item("step_type", _stepType, stepTypes);
|
||||
|
||||
// TODO: Consider putting these under a gcode: hierarchy level? Or motion control?
|
||||
handler.item("laser_mode", _laserMode);
|
||||
handler.item("arc_tolerance", _arcTolerance);
|
||||
@@ -110,6 +105,10 @@ namespace Machine {
|
||||
_spi = new SPIBus();
|
||||
}
|
||||
|
||||
if (_stepping == nullptr) {
|
||||
_stepping = new Stepping();
|
||||
}
|
||||
|
||||
// We do not auto-create an I2SO bus config node
|
||||
// Only if an i2so section is present will config->_i2so be non-null
|
||||
|
||||
|
@@ -28,7 +28,7 @@
|
||||
#include "../Probe.h"
|
||||
#include "../SDCard.h"
|
||||
#include "../Spindles/Spindle.h"
|
||||
#include "../EnumItem.h"
|
||||
#include "../Stepping.h"
|
||||
#include "../Stepper.h"
|
||||
#include "../Logging.h"
|
||||
#include "../Config.h"
|
||||
@@ -47,6 +47,7 @@ namespace Machine {
|
||||
Axes* _axes = nullptr;
|
||||
SPIBus* _spi = nullptr;
|
||||
I2SOBus* _i2so = nullptr;
|
||||
Stepping* _stepping = nullptr;
|
||||
CoolantControl* _coolant = nullptr;
|
||||
Probe* _probe = nullptr;
|
||||
Communications* _comms = nullptr;
|
||||
@@ -57,19 +58,13 @@ namespace Machine {
|
||||
|
||||
Spindles::SpindleList _spindles;
|
||||
|
||||
int _pulseMicroSeconds = 3;
|
||||
int _directionDelayMicroSeconds = 0;
|
||||
int _disableDelayMicroSeconds = 0;
|
||||
|
||||
bool _laserMode = false;
|
||||
float _arcTolerance = 0.002f;
|
||||
float _junctionDeviation = 0.01f;
|
||||
uint8_t _idleTime = 255;
|
||||
bool _verboseErrors = false;
|
||||
bool _reportInches = false;
|
||||
bool _homingInitLock = true;
|
||||
int _softwareDebounceMs = 0;
|
||||
int _stepType = ST_RMT;
|
||||
bool _laserMode = false;
|
||||
float _arcTolerance = 0.002f;
|
||||
float _junctionDeviation = 0.01f;
|
||||
bool _verboseErrors = false;
|
||||
bool _reportInches = false;
|
||||
bool _homingInitLock = true;
|
||||
int _softwareDebounceMs = 0;
|
||||
|
||||
// Enables a special set of M-code commands that enables and disables the parking motion.
|
||||
// These are controlled by `M56`, `M56 P1`, or `M56 Px` to enable and `M56 P0` to disable.
|
||||
|
@@ -329,9 +329,7 @@ GCUpdatePos mc_probe_cycle(float* target, plan_line_data_t* pl_data, uint8_t par
|
||||
return GCUpdatePos::None; // Return if system reset has been issued.
|
||||
}
|
||||
|
||||
int save_stepper = config->_stepType; /* remember the stepper */
|
||||
|
||||
config->_axes->beginLowLatency();
|
||||
config->_stepping->beginLowLatency();
|
||||
|
||||
// Initialize probing control variables
|
||||
bool is_probe_away = bit_istrue(parser_flags, GCParserProbeIsAway);
|
||||
@@ -343,7 +341,7 @@ GCUpdatePos mc_probe_cycle(float* target, plan_line_data_t* pl_data, uint8_t par
|
||||
if (config->_probe->tripped()) {
|
||||
sys_rt_exec_alarm = ExecAlarm::ProbeFailInitial;
|
||||
protocol_execute_realtime();
|
||||
config->_axes->endLowLatency();
|
||||
config->_stepping->endLowLatency();
|
||||
return GCUpdatePos::None; // Nothing else to do but bail.
|
||||
}
|
||||
// Setup and queue probing motion. Auto cycle-start should not start the cycle.
|
||||
@@ -356,12 +354,12 @@ GCUpdatePos mc_probe_cycle(float* target, plan_line_data_t* pl_data, uint8_t par
|
||||
do {
|
||||
protocol_execute_realtime();
|
||||
if (sys.abort) {
|
||||
config->_axes->endLowLatency();
|
||||
config->_stepping->endLowLatency();
|
||||
return GCUpdatePos::None; // Check for system abort
|
||||
}
|
||||
} while (sys.state != State::Idle);
|
||||
|
||||
config->_axes->endLowLatency();
|
||||
config->_stepping->endLowLatency();
|
||||
|
||||
// Probing cycle complete!
|
||||
// Set state variables and error out, if the probe failed and cycle with error is enabled.
|
||||
@@ -467,8 +465,6 @@ void mc_reset() {
|
||||
}
|
||||
ganged_mode = gangDual; // in case an error occurred during squaring
|
||||
|
||||
if (config->_stepType == ST_I2S_STREAM) {
|
||||
i2s_out_reset();
|
||||
}
|
||||
config->_stepping->reset();
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,10 @@
|
||||
|
||||
#include "../Report.h"
|
||||
#include "../Machine/MachineConfig.h"
|
||||
#include "../Stepper.h" // ST_I2S_*
|
||||
#include "../Stepping.h" // config->_stepping->_engine
|
||||
|
||||
using namespace Machine;
|
||||
|
||||
namespace Motors {
|
||||
rmt_item32_t StandardStepper::rmtItem[2];
|
||||
@@ -57,8 +61,8 @@ namespace Motors {
|
||||
|
||||
_dir_pin.setAttr(Pin::Attr::Output);
|
||||
|
||||
// stepping->init(_step_pin);
|
||||
if (config->_stepType == ST_RMT) {
|
||||
auto stepping = config->_stepping;
|
||||
if (stepping->_engine == Stepping::RMT) {
|
||||
rmtConfig.rmt_mode = RMT_MODE_TX;
|
||||
rmtConfig.clk_div = 20;
|
||||
rmtConfig.mem_block_num = 2;
|
||||
@@ -69,8 +73,8 @@ namespace Motors {
|
||||
rmtConfig.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
||||
rmtConfig.tx_config.idle_output_en = true;
|
||||
|
||||
rmtItem[0].duration0 = config->_directionDelayMicroSeconds < 1 ? 1 : config->_directionDelayMicroSeconds * 4;
|
||||
rmtItem[0].duration1 = 4 * config->_pulseMicroSeconds;
|
||||
rmtItem[0].duration0 = stepping->_directionDelayUsecs < 1 ? 1 : stepping->_directionDelayUsecs * 4;
|
||||
rmtItem[0].duration1 = 4 * stepping->_pulseUsecs;
|
||||
rmtItem[1].duration0 = 0;
|
||||
rmtItem[1].duration1 = 0;
|
||||
|
||||
@@ -105,7 +109,7 @@ namespace Motors {
|
||||
}
|
||||
|
||||
void IRAM_ATTR StandardStepper::step() {
|
||||
if (config->_stepType == ST_RMT) {
|
||||
if (config->_stepping->_engine == Stepping::RMT) {
|
||||
RMT.conf_ch[_rmt_chan_num].conf1.mem_rd_rst = 1;
|
||||
RMT.conf_ch[_rmt_chan_num].conf1.tx_start = 1;
|
||||
} else {
|
||||
@@ -114,7 +118,7 @@ namespace Motors {
|
||||
}
|
||||
|
||||
void IRAM_ATTR StandardStepper::unstep() {
|
||||
if (config->_stepType != ST_RMT) {
|
||||
if (config->_stepping->_engine != Stepping::RMT) {
|
||||
_step_pin.off();
|
||||
}
|
||||
}
|
||||
@@ -127,4 +131,13 @@ namespace Motors {
|
||||
namespace {
|
||||
MotorFactory::InstanceBuilder<StandardStepper> registration("standard_stepper");
|
||||
}
|
||||
|
||||
void StandardStepper::validate() const {
|
||||
Assert(_step_pin.defined(), "Step pin should be configured.");
|
||||
Assert(_dir_pin.defined(), "Direction pin should be configured.");
|
||||
bool isI2SO = config->_stepping->_engine == Stepping::I2S_STREAM || config->_stepping->_engine == Stepping::I2S_STATIC;
|
||||
Assert(!isI2SO || _step_pin.name().startsWith("I2SO"), "Step pin must be an I2SO pin");
|
||||
Assert(!isI2SO || _dir_pin.name().startsWith("I2SO"), "Direction pin must be an I2SO pin");
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Machine/MachineConfig.h" // config->_stepType
|
||||
#include "../Stepper.h" // ST_I2S_*
|
||||
#include "Motor.h"
|
||||
|
||||
#include <driver/rmt.h>
|
||||
@@ -34,13 +32,7 @@ namespace Motors {
|
||||
Pin _disable_pin;
|
||||
|
||||
// Configuration handlers:
|
||||
void validate() const override {
|
||||
Assert(_step_pin.defined(), "Step pin should be configured.");
|
||||
Assert(_dir_pin.defined(), "Direction pin should be configured.");
|
||||
bool isI2SO = config->_stepType == ST_I2S_STREAM || config->_stepType == ST_I2S_STATIC;
|
||||
Assert(!isI2SO || _step_pin.name().startsWith("I2SO"), "Step pin must be an I2SO pin");
|
||||
Assert(!isI2SO || _dir_pin.name().startsWith("I2SO"), "Direction pin must be an I2SO pin");
|
||||
}
|
||||
void validate() const override;
|
||||
|
||||
void group(Configuration::HandlerBase& handler) override {
|
||||
handler.item("step", _step_pin);
|
||||
|
@@ -272,11 +272,11 @@ Error home(int cycle) {
|
||||
}
|
||||
sys.state = State::Homing; // Set system state variable
|
||||
|
||||
config->_axes->beginLowLatency();
|
||||
config->_stepping->beginLowLatency();
|
||||
|
||||
mc_homing_cycle(cycle);
|
||||
|
||||
config->_axes->endLowLatency();
|
||||
config->_stepping->endLowLatency();
|
||||
|
||||
if (!sys.abort) { // Execute startup scripts after successful homing.
|
||||
sys.state = State::Idle; // Set to IDLE when complete.
|
||||
|
@@ -26,12 +26,10 @@
|
||||
#include "Stepper.h"
|
||||
|
||||
#include "Machine/MachineConfig.h"
|
||||
#include "Stepping.h"
|
||||
#include "StepperPrivate.h"
|
||||
#include "Planner.h"
|
||||
#include "I2SOut.h" // i2s_out_push_sample
|
||||
#include "Report.h" // info_serial
|
||||
|
||||
#include <atomic>
|
||||
#include "Report.h" // info_serial
|
||||
#include <Arduino.h> // IRAM_ATTR
|
||||
|
||||
using namespace Stepper;
|
||||
@@ -88,9 +86,6 @@ static volatile uint8_t segment_buffer_tail;
|
||||
static uint8_t segment_buffer_head;
|
||||
static uint8_t segment_next_head;
|
||||
|
||||
// Used to avoid ISR nesting of the "Stepper Driver Interrupt". Should never occur though.
|
||||
static std::atomic<bool> busy;
|
||||
|
||||
// Pointers for the step segment being prepped from the planner buffer. Accessed only by the
|
||||
// main program. Pointers may be planning segments or planner blocks ahead of what being executed.
|
||||
static plan_block_t* pl_block; // Pointer to the planner block being prepped
|
||||
@@ -108,7 +103,7 @@ bool Stepper::shouldDisable() {
|
||||
// space. Using "timer() > EndTime" fails across the positive to
|
||||
// negative transition using signed comparison, and across the
|
||||
// negative to positive transition using unsigned.
|
||||
return isIdle && config->_idleTime != 255 && (esp_timer_get_time() - idleEndTime) > 0;
|
||||
return isIdle && config->_stepping->_idleMsecs != 255 && (esp_timer_get_time() - idleEndTime) > 0;
|
||||
}
|
||||
|
||||
// Segment preparation data struct. Contains all the necessary information to compute new segments
|
||||
@@ -142,10 +137,6 @@ typedef struct {
|
||||
} st_prep_t;
|
||||
static st_prep_t prep;
|
||||
|
||||
EnumItem stepTypes[] = {
|
||||
{ ST_TIMED, "Timed" }, { ST_RMT, "RMT" }, { ST_I2S_STATIC, "I2S_static" }, { ST_I2S_STREAM, "I2S_stream" }, EnumItem(ST_RMT)
|
||||
};
|
||||
|
||||
/* "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. Grbl employs
|
||||
the venerable Bresenham line algorithm to manage and exactly synchronize multi-axis moves.
|
||||
Unlike the popular DDA algorithm, the Bresenham algorithm is not susceptible to numerical
|
||||
@@ -202,58 +193,6 @@ EnumItem stepTypes[] = {
|
||||
|
||||
*/
|
||||
|
||||
// Forward references to functions that are only used herein
|
||||
static void timerWritePeriod(uint16_t timerTicks);
|
||||
static void timerInit();
|
||||
static void timerStart();
|
||||
static void timerStop();
|
||||
|
||||
// Stepper timer configuration
|
||||
const int stepTimerNumber = 0;
|
||||
hw_timer_t* stepTimer = nullptr; // Handle
|
||||
// autoReload true might give better step timing - but it also
|
||||
// might cause problems if an interrupt takes too long
|
||||
const bool autoReload = true;
|
||||
|
||||
static void pulse_func();
|
||||
|
||||
// Counts stepper ISR invocations. This variable can be inspected
|
||||
// from the mainline code to determine if the stepper ISR is running,
|
||||
// since printing from the ISR is not a good idea.
|
||||
uint32_t Stepper::isr_count = 0;
|
||||
|
||||
// TODO: Replace direct updating of the int32 position counters in the ISR somehow. Perhaps use smaller
|
||||
// int8 variables and update position counters only when a segment completes. This can get complicated
|
||||
// with probing and homing cycles that require true real-time positions.
|
||||
void IRAM_ATTR onStepperDriverTimer() {
|
||||
// Timer ISR, normally takes a step.
|
||||
|
||||
// The intermediate handler clears the timer interrupt so we need not do it here
|
||||
|
||||
bool expected = false;
|
||||
if (busy.compare_exchange_strong(expected, true)) {
|
||||
++isr_count;
|
||||
|
||||
// Using autoReload results is less timing jitter so it is
|
||||
// probably best to have it on. We keep the variable for
|
||||
// convenience in debugging.
|
||||
if (!autoReload) {
|
||||
timerWrite(stepTimer, 0ULL);
|
||||
}
|
||||
|
||||
// It is tempting to defer this until after pulse_func(),
|
||||
// but if pulse_func() determines that no more stepping
|
||||
// is required and disables the timer, then that will be undone
|
||||
// if the re-enable happens afterwards.
|
||||
|
||||
timerAlarmEnable(stepTimer);
|
||||
|
||||
pulse_func();
|
||||
|
||||
busy.store(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This phase of the ISR should ONLY create the pulses for the steppers.
|
||||
* This prevents jitter caused by the interval between the start of the
|
||||
@@ -261,15 +200,9 @@ void IRAM_ATTR onStepperDriverTimer() {
|
||||
* call to this method that might cause variation in the timing. The aim
|
||||
* is to keep pulse timing as regular as possible.
|
||||
*/
|
||||
static void IRAM_ATTR pulse_func() {
|
||||
void IRAM_ATTR Stepper::pulse_func() {
|
||||
auto n_axis = config->_axes->_numberAxis;
|
||||
|
||||
// If we are using GPIO stepping as opposed to RMT, record the
|
||||
// time that we turned on the step pins so we can turn them off
|
||||
// at the end of this routine without incurring another interrupt.
|
||||
// This is unnecessary with RMT and I2S stepping since both of
|
||||
// those methods time the turn off automatically.
|
||||
uint64_t step_pulse_start_time = esp_timer_get_time();
|
||||
config->_axes->step(st.step_outbits, st.dir_outbits);
|
||||
|
||||
// If there is no step segment, attempt to pop one from the stepper buffer
|
||||
@@ -279,7 +212,7 @@ static void IRAM_ATTR 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.
|
||||
timerWritePeriod(st.exec_segment->isrPeriod);
|
||||
config->_stepping->setTimerPeriod(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.
|
||||
@@ -348,41 +281,7 @@ static void IRAM_ATTR pulse_func() {
|
||||
}
|
||||
}
|
||||
|
||||
// stepping->unStep(pulseMicros);
|
||||
uint64_t endMicros;
|
||||
switch (config->_stepType) {
|
||||
case ST_I2S_STREAM:
|
||||
// Generate the number of pulses needed to span pulse_microseconds
|
||||
i2s_out_push_sample(config->_pulseMicroSeconds);
|
||||
config->_axes->unstep();
|
||||
break;
|
||||
case ST_I2S_STATIC:
|
||||
case ST_TIMED:
|
||||
endMicros = config->_pulseMicroSeconds + step_pulse_start_time;
|
||||
// wait for step pulse time to complete...some time expired during code above
|
||||
while ((esp_timer_get_time() - endMicros) < 0) {
|
||||
NOP(); // spin here until time to turn off step
|
||||
}
|
||||
config->_axes->unstep();
|
||||
break;
|
||||
case ST_RMT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Stepper::init() {
|
||||
busy.store(false);
|
||||
|
||||
info_serial("Axis count %d", config->_axes->_numberAxis);
|
||||
info_serial("Step type:%s Pulse:%dus Dsbl Delay:%dus Dir Delay:%dus",
|
||||
// stepping->name(),
|
||||
stepTypes[config->_stepType].name,
|
||||
config->_pulseMicroSeconds,
|
||||
config->_disableDelayMicroSeconds,
|
||||
config->_directionDelayMicroSeconds);
|
||||
|
||||
// Other stepper use timer interrupt
|
||||
timerInit();
|
||||
config->_axes->unstep();
|
||||
}
|
||||
|
||||
// enabled. Startup init and limits call this function but shouldn't start the cycle.
|
||||
@@ -392,17 +291,14 @@ void Stepper::wake_up() {
|
||||
config->_axes->set_disable(false);
|
||||
isIdle = false;
|
||||
|
||||
// Enable Stepper Driver Interrupt
|
||||
timerStart();
|
||||
// Enable Stepping Driver Interrupt
|
||||
config->_stepping->startTimer();
|
||||
}
|
||||
|
||||
// Reset and clear stepper subsystem variables
|
||||
void Stepper::reset() {
|
||||
// Initialize stepper driver idle state.
|
||||
// stepping->reset();
|
||||
if (config->_stepType == ST_I2S_STREAM) {
|
||||
i2s_out_reset();
|
||||
}
|
||||
// Initialize Stepping driver idle state.
|
||||
config->_stepping->reset();
|
||||
|
||||
go_idle();
|
||||
// Initialize stepper algorithm variables.
|
||||
@@ -420,11 +316,12 @@ void Stepper::reset() {
|
||||
|
||||
// Stepper shutdown
|
||||
void IRAM_ATTR Stepper::go_idle() {
|
||||
// Disable Stepper Driver Interrupt. Allow Stepper Port Reset Interrupt to finish, if active.
|
||||
timerStop();
|
||||
// Disable Stepping Driver Interrupt.
|
||||
config->_stepping->stopTimer();
|
||||
|
||||
// Set stepper driver idle state, disabled or enabled, depending on settings and circumstances.
|
||||
if (((config->_idleTime != 255) || sys_rt_exec_alarm != ExecAlarm::None || sys.state == State::Sleep) && sys.state != State::Homing) {
|
||||
if (((config->_stepping->_idleMsecs != 255) || sys_rt_exec_alarm != ExecAlarm::None || sys.state == State::Sleep) &&
|
||||
sys.state != State::Homing) {
|
||||
// Force stepper dwell to lock axes for a defined amount of time to ensure the axes come to a complete
|
||||
// stop and not drift from residual inertial forces at the end of the last movement.
|
||||
|
||||
@@ -433,7 +330,7 @@ void IRAM_ATTR Stepper::go_idle() {
|
||||
} else {
|
||||
// Setup for shouldDisable()
|
||||
isIdle = true;
|
||||
idleEndTime = esp_timer_get_time() + (config->_idleTime * 1000); // * 1000 because the time is in uSecs
|
||||
idleEndTime = esp_timer_get_time() + (config->_stepping->_idleMsecs * 1000); // * 1000 because the time is in uSecs
|
||||
}
|
||||
} else {
|
||||
config->_axes->set_disable(false);
|
||||
@@ -846,7 +743,7 @@ void Stepper::prep_buffer() {
|
||||
// Compute CPU cycles per step for the prepped segment.
|
||||
// fStepperTimer is in units of timerTicks/sec, so the dimensional analysis is
|
||||
// timerTicks/sec * 60 sec/minute * minutes = timerTicks
|
||||
uint32_t timerTicks = uint32_t(ceil((fStepperTimer * 60) * inv_rate)); // (timerTicks/step)
|
||||
uint32_t timerTicks = uint32_t(ceil((Machine::Stepping::fStepperTimer * 60) * inv_rate)); // (timerTicks/step)
|
||||
int level;
|
||||
|
||||
// Compute step timing and multi-axis smoothing level.
|
||||
@@ -913,49 +810,6 @@ float Stepper::get_realtime_rate() {
|
||||
}
|
||||
}
|
||||
|
||||
// The argument is in units of ticks of the timer that generates ISRs
|
||||
static void IRAM_ATTR timerWritePeriod(uint16_t timerTicks) {
|
||||
// stepping->setPeriod(uint16_t timerTicks);
|
||||
if (config->_stepType == ST_I2S_STREAM) {
|
||||
// 1 tick = fTimers / fStepperTimer
|
||||
// Pulse ISR is called for each tick of alarm_val.
|
||||
// The argument to i2s_out_set_pulse_period is in units of microseconds
|
||||
i2s_out_set_pulse_period(((uint32_t)timerTicks) / ticksPerMicrosecond);
|
||||
} else {
|
||||
timerAlarmWrite(stepTimer, (uint64_t)timerTicks, autoReload);
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR timerInit() {
|
||||
const bool isEdge = false;
|
||||
const bool countUp = true;
|
||||
|
||||
// Prepare stepping interrupt callbacks. The one that is actually
|
||||
// used is determined by timerStart() and timerStop()
|
||||
|
||||
// Register pulse_func with the I2S subsystem
|
||||
i2s_out_set_pulse_callback(pulse_func);
|
||||
|
||||
// Setup a timer for direct stepping
|
||||
stepTimer = timerBegin(stepTimerNumber, fTimers / fStepperTimer, countUp);
|
||||
timerAttachInterrupt(stepTimer, onStepperDriverTimer, isEdge);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR timerStart() {
|
||||
// stepping->start();
|
||||
if (config->_stepType == ST_I2S_STREAM) {
|
||||
i2s_out_set_stepping();
|
||||
} else {
|
||||
timerWrite(stepTimer, 0ULL);
|
||||
timerAlarmEnable(stepTimer);
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR timerStop() {
|
||||
// stepping->stop();
|
||||
if (config->_stepType == ST_I2S_STREAM) {
|
||||
i2s_out_set_passthrough();
|
||||
} else if (stepTimer) {
|
||||
timerAlarmDisable(stepTimer);
|
||||
}
|
||||
void Stepper::init() {
|
||||
config->_stepping->init();
|
||||
}
|
||||
|
@@ -28,19 +28,13 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum stepper_id_t {
|
||||
ST_TIMED = 0,
|
||||
ST_RMT,
|
||||
ST_I2S_STATIC,
|
||||
ST_I2S_STREAM,
|
||||
};
|
||||
|
||||
namespace Stepper {
|
||||
// Is it time to disable the steppers?
|
||||
bool shouldDisable();
|
||||
|
||||
void init();
|
||||
void switch_mode(stepper_id_t new_stepper);
|
||||
|
||||
void pulse_func();
|
||||
|
||||
// Enable steppers, but cycle does not start unless called by motion control or realtime command.
|
||||
void wake_up();
|
||||
@@ -69,5 +63,3 @@ namespace Stepper {
|
||||
extern uint32_t isr_count; // for debugging only
|
||||
}
|
||||
// private
|
||||
|
||||
extern EnumItem stepTypes[];
|
||||
|
@@ -23,11 +23,6 @@ struct PrepFlag {
|
||||
uint8_t decelOverride : 1;
|
||||
};
|
||||
|
||||
// fStepperTimer should be an integer divisor of the bus speed, i.e. of fTimers
|
||||
const uint32_t fTimers = 80000000; // the frequency of ESP32 timers
|
||||
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
|
||||
@@ -39,5 +34,5 @@ const int ticksPerMicrosecond = fStepperTimer / 1000000;
|
||||
// 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.
|
||||
|
||||
const uint32_t amassThreshold = fStepperTimer / 8000;
|
||||
const uint32_t amassThreshold = Machine::Stepping::fStepperTimer / 8000;
|
||||
const int maxAmassLevel = 3; // Each level increase doubles the threshold
|
||||
|
213
Grbl_Esp32/src/Stepping.cpp
Normal file
213
Grbl_Esp32/src/Stepping.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include <Arduino.h>
|
||||
#include "I2SOut.h"
|
||||
#include "EnumItem.h"
|
||||
#include "Stepping.h"
|
||||
#include "Stepper.h"
|
||||
#include "Report.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace Machine {
|
||||
|
||||
EnumItem stepTypes[] = { { Stepping::TIMED, "Timed" },
|
||||
{ Stepping::RMT, "RMT" },
|
||||
{ Stepping::I2S_STATIC, "I2S_static" },
|
||||
{ Stepping::I2S_STREAM, "I2S_stream" },
|
||||
EnumItem(Stepping::RMT) };
|
||||
|
||||
static std::atomic<bool> busy;
|
||||
|
||||
void Stepping::init() {
|
||||
info_serial("Step type:%s Pulse:%dus Dsbl Delay:%dus Dir Delay:%dus Idle Delay:%dms",
|
||||
// stepping->name(),
|
||||
stepTypes[_engine].name,
|
||||
_pulseUsecs,
|
||||
_disableDelayUsecs,
|
||||
_directionDelayUsecs,
|
||||
_idleMsecs);
|
||||
|
||||
// Prepare stepping interrupt callbacks. The one that is actually
|
||||
// used is determined by timerStart() and timerStop()
|
||||
|
||||
const bool isEdge = false;
|
||||
const bool countUp = true;
|
||||
|
||||
// Setup a timer for direct stepping
|
||||
stepTimer = timerBegin(stepTimerNumber, fTimers / fStepperTimer, countUp);
|
||||
timerAttachInterrupt(stepTimer, onStepperDriverTimer, isEdge);
|
||||
|
||||
// Register pulse_func with the I2S subsystem
|
||||
// This could be done via the linker.
|
||||
// i2s_out_set_pulse_callback(Stepper::pulse_func);
|
||||
|
||||
busy.store(false);
|
||||
}
|
||||
|
||||
// Wait for motion to complete; the axes can still be moving
|
||||
// after the data has been sent to the stepping engine, due
|
||||
// to queuing delays.
|
||||
void Stepping::synchronize() {
|
||||
if (_engine == I2S_STREAM) {
|
||||
i2s_out_delay();
|
||||
// XXX instead of a delay, we could sense when the DMA and
|
||||
// FIFO have drained. It might be as simple as waiting for
|
||||
// I2SO.conf1.tx_start == 0, while yielding.
|
||||
// delay_ms(I2S_OUT_DELAY_MS);
|
||||
}
|
||||
}
|
||||
void Stepping::reset() {
|
||||
if (_engine == I2S_STREAM) {
|
||||
i2s_out_reset();
|
||||
}
|
||||
}
|
||||
void Stepping::beginLowLatency() {
|
||||
_switchedStepper = _engine == I2S_STREAM;
|
||||
if (_switchedStepper) {
|
||||
_engine = I2S_STATIC;
|
||||
}
|
||||
}
|
||||
void Stepping::endLowLatency() {
|
||||
if (_switchedStepper) {
|
||||
if (i2s_out_get_pulser_status() != PASSTHROUGH) {
|
||||
// Called during streaming. Stop streaming.
|
||||
// debug_serial("Stop the I2S streaming and switch to the passthrough mode.");
|
||||
i2s_out_set_passthrough();
|
||||
i2s_out_delay(); // Wait for a change in mode.
|
||||
}
|
||||
_engine = I2S_STREAM;
|
||||
}
|
||||
}
|
||||
void Stepping::spinDelay(int64_t start_time, uint32_t durationUs) {
|
||||
int64_t endTime = start_time + durationUs;
|
||||
while ((esp_timer_get_time() - endTime) < 0) {
|
||||
NOP();
|
||||
}
|
||||
}
|
||||
void Stepping::waitPulse() {
|
||||
uint64_t pulseEndTime;
|
||||
switch (_engine) {
|
||||
case I2S_STREAM:
|
||||
// Generate the number of pulses needed to span pulse_microseconds
|
||||
i2s_out_push_sample(_pulseUsecs);
|
||||
break;
|
||||
case I2S_STATIC:
|
||||
case TIMED:
|
||||
spinDelay(_stepPulseStartTime, _pulseUsecs);
|
||||
break;
|
||||
case RMT:
|
||||
// RMT generates the trailing edges in hardware
|
||||
return;
|
||||
}
|
||||
}
|
||||
void Stepping::waitDirection() {
|
||||
if (_directionDelayUsecs) {
|
||||
// Stepper drivers need some time between changing direction and doing a pulse.
|
||||
switch (_engine) {
|
||||
case stepper_id_t::I2S_STREAM:
|
||||
i2s_out_push_sample(_directionDelayUsecs);
|
||||
break;
|
||||
case stepper_id_t::I2S_STATIC:
|
||||
case stepper_id_t::TIMED: {
|
||||
// wait for step pulse time to complete...some time expired during code above
|
||||
//
|
||||
// If we are using GPIO stepping as opposed to RMT, record the
|
||||
// time that we turned on the direction pins so we can delay a bit.
|
||||
// If we are using RMT, we can't delay here.
|
||||
spinDelay(esp_timer_get_time(), _directionDelayUsecs);
|
||||
break;
|
||||
case stepper_id_t::RMT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Stepping::startPulseTimer() {
|
||||
switch (_engine) {
|
||||
case stepper_id_t::I2S_STREAM:
|
||||
break;
|
||||
case stepper_id_t::I2S_STATIC:
|
||||
case stepper_id_t::TIMED:
|
||||
_stepPulseStartTime = esp_timer_get_time();
|
||||
break;
|
||||
case stepper_id_t::RMT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The argument is in units of ticks of the timer that generates ISRs
|
||||
void IRAM_ATTR Stepping::setTimerPeriod(uint16_t timerTicks) {
|
||||
if (_engine == I2S_STREAM) {
|
||||
// 1 tick = fTimers / fStepperTimer
|
||||
// Pulse ISR is called for each tick of alarm_val.
|
||||
// The argument to i2s_out_set_pulse_period is in units of microseconds
|
||||
i2s_out_set_pulse_period(((uint32_t)timerTicks) / ticksPerMicrosecond);
|
||||
} else {
|
||||
timerAlarmWrite(stepTimer, (uint64_t)timerTicks, autoReload);
|
||||
}
|
||||
}
|
||||
void Stepping::startTimer() {
|
||||
if (_engine == I2S_STREAM) {
|
||||
i2s_out_set_stepping();
|
||||
} else {
|
||||
timerWrite(stepTimer, 0ULL);
|
||||
timerAlarmEnable(stepTimer);
|
||||
}
|
||||
}
|
||||
void Stepping::stopTimer() {
|
||||
if (_engine == I2S_STREAM) {
|
||||
i2s_out_set_passthrough();
|
||||
} else if (stepTimer) {
|
||||
timerAlarmDisable(stepTimer);
|
||||
}
|
||||
}
|
||||
|
||||
// Stepper timer configuration
|
||||
hw_timer_t* Stepping::stepTimer = nullptr; // Handle
|
||||
|
||||
// Counts stepper ISR invocations. This variable can be inspected
|
||||
// from the mainline code to determine if the stepper ISR is running,
|
||||
// since printing from the ISR is not a good idea.
|
||||
uint32_t Stepping::isr_count = 0;
|
||||
|
||||
// Used to avoid ISR nesting of the "Stepper Driver Interrupt". Should never occur though.
|
||||
// TODO: Replace direct updating of the int32 position counters in the ISR somehow. Perhaps use smaller
|
||||
// int8 variables and update position counters only when a segment completes. This can get complicated
|
||||
// with probing and homing cycles that require true real-time positions.
|
||||
void IRAM_ATTR Stepping::onStepperDriverTimer() {
|
||||
// Timer ISR, normally takes a step.
|
||||
|
||||
// The intermediate handler clears the timer interrupt so we need not do it here
|
||||
|
||||
bool expected = false;
|
||||
if (busy.compare_exchange_strong(expected, true)) {
|
||||
++isr_count;
|
||||
|
||||
// Using autoReload results is less timing jitter so it is
|
||||
// probably best to have it on. We keep the variable for
|
||||
// convenience in debugging.
|
||||
if (!autoReload) {
|
||||
timerWrite(stepTimer, 0ULL);
|
||||
}
|
||||
|
||||
// It is tempting to defer this until after pulse_func(),
|
||||
// but if pulse_func() determines that no more stepping
|
||||
// is required and disables the timer, then that will be undone
|
||||
// if the re-enable happens afterwards.
|
||||
|
||||
timerAlarmEnable(stepTimer);
|
||||
|
||||
Stepper::pulse_func();
|
||||
|
||||
busy.store(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Stepping::group(Configuration::HandlerBase& handler) {
|
||||
handler.item("engine", _engine, stepTypes);
|
||||
handler.item("idle_ms", _idleMsecs);
|
||||
handler.item("pulse_us", _pulseUsecs);
|
||||
handler.item("dir_delay_us", _directionDelayUsecs);
|
||||
handler.item("disable_delay_us", _disableDelayUsecs);
|
||||
}
|
||||
}
|
87
Grbl_Esp32/src/Stepping.h
Normal file
87
Grbl_Esp32/src/Stepping.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
Part of Grbl_ESP32
|
||||
2021 - Stefan de Bruijn, Mitch Bradley
|
||||
|
||||
Grbl_ESP32 is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Grbl_ESP32 is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Grbl_ESP32. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Configuration/Configurable.h"
|
||||
// #include <atomic>
|
||||
|
||||
namespace Machine {
|
||||
class Stepping : public Configuration::Configurable {
|
||||
public:
|
||||
// fStepperTimer should be an integer divisor of the bus speed, i.e. of fTimers
|
||||
static const uint32_t fStepperTimer = 20000000; // frequency of step pulse timer
|
||||
|
||||
private:
|
||||
static const int stepTimerNumber = 0;
|
||||
static const bool autoReload = true;
|
||||
static hw_timer_t* stepTimer;
|
||||
static void onStepperDriverTimer();
|
||||
|
||||
static const uint32_t fTimers = 80000000; // the frequency of ESP32 timers
|
||||
static const int ticksPerMicrosecond = fStepperTimer / 1000000;
|
||||
|
||||
bool _switchedStepper = false;
|
||||
int64_t _stepPulseStartTime;
|
||||
|
||||
static void spinDelay(int64_t start_time, uint32_t duration);
|
||||
|
||||
public:
|
||||
// Counts stepper ISR invocations. This variable can be inspected
|
||||
// from the mainline code to determine if the stepper ISR is running,
|
||||
// since printing from the ISR is not a good idea.
|
||||
static uint32_t isr_count;
|
||||
|
||||
enum stepper_id_t {
|
||||
TIMED = 0,
|
||||
RMT,
|
||||
I2S_STATIC,
|
||||
I2S_STREAM,
|
||||
};
|
||||
|
||||
Stepping() = default;
|
||||
|
||||
uint8_t _idleMsecs = 255;
|
||||
uint32_t _pulseUsecs = 3;
|
||||
uint32_t _directionDelayUsecs = 0;
|
||||
uint32_t _disableDelayUsecs = 0;
|
||||
|
||||
int _engine = RMT;
|
||||
|
||||
// Interfaces to stepping engine
|
||||
void init();
|
||||
|
||||
void synchronize(); // Wait for motion to complete
|
||||
void reset(); // Clean up old state and start fresh
|
||||
void beginLowLatency();
|
||||
void endLowLatency();
|
||||
void startPulseTimer();
|
||||
void waitPulse(); // Wait for pulse length
|
||||
void waitDirection(); // Wait for direction delay
|
||||
void waitMotion(); // Wait for motion to complete
|
||||
|
||||
// Timers
|
||||
void setTimerPeriod(uint16_t timerTicks);
|
||||
void startTimer();
|
||||
void stopTimer();
|
||||
|
||||
// Configuration system helpers:
|
||||
void group(Configuration::HandlerBase& handler) override;
|
||||
};
|
||||
}
|
||||
extern EnumItem stepTypes[];
|
@@ -10,7 +10,7 @@ namespace Configuration {
|
||||
"yaml_wiki: \"https://github.com/bdring/Grbl_Esp32/wiki/YAML-Config-File\"\n"
|
||||
"\n"
|
||||
"idle_time: 250\n"
|
||||
"step_type: rmt\n"
|
||||
"engine: rmt\n"
|
||||
"dir_delay_microseconds: 1\n"
|
||||
"pulse_microseconds: 2\n"
|
||||
"disable_delay_us: 0\n"
|
||||
@@ -63,7 +63,7 @@ namespace Configuration {
|
||||
p.Tokenize();
|
||||
|
||||
Assert(!p.Eof(), "No EOF expected");
|
||||
Assert(p.key().equals("step_type"), "Expected 'step_type'");
|
||||
Assert(p.key().equals("engine"), "Expected 'engine'");
|
||||
p.Tokenize();
|
||||
|
||||
Assert(!p.Eof(), "No EOF expected");
|
||||
|
Reference in New Issue
Block a user