diff --git a/FluidNC/esp32/i2s_engine.c b/FluidNC/esp32/i2s_engine.c index 55e701a9d..4dae502f9 100644 --- a/FluidNC/esp32/i2s_engine.c +++ b/FluidNC/esp32/i2s_engine.c @@ -1,10 +1,18 @@ // Copyright (c) 2024 - Mitch Bradley // Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. -// Stepping engine that uses the I2S FIFO +// Stepping engine that uses the I2S FIFO. An interrupt service routine runs when +// the FIFO is below a set threshold. The ISR pushes samples into the FIFO, representing +// step pulses and inter-pulse delays. There are variables for the value to push for +// a pulse, the number of samples for that pulse, the value to push to the delay, and +// the number of samples for the delay. When the delay is done, the ISR calls the Stepper +// pulse_func to determine the new values of those variables. The FIFO lets the ISR stay +// just far enough ahead so the information is always ready, but not so far ahead to cause +// latency problems. #include "Driver/step_engine.h" #include "Driver/i2s_out.h" +#include "Driver/StepTimer.h" #include "hal/i2s_hal.h" #include @@ -14,7 +22,6 @@ #include // IRAM_ATTR #include -// #include #include #include @@ -22,6 +29,8 @@ #include #include "Driver/fluidnc_gpio.h" +#include "esp_intr_alloc.h" + /* 16-bit mode: 1000000 usec / ((160000000 Hz) / 10 / 2) x 16 bit/pulse x 2(stereo) = 4 usec/pulse */ /* 32-bit mode: 1000000 usec / ((160000000 Hz) / 5 / 2) x 32 bit/pulse x 2(stereo) = 4 usec/pulse */ const uint32_t I2S_OUT_USEC_PER_PULSE = 2; @@ -50,54 +59,12 @@ static std::atomic i2s_out_port_data = ATOMIC_VAR_INIT(0); const int I2S_SAMPLE_SIZE = 4; /* 4 bytes, 32 bits per sample */ -// inner lock -static portMUX_TYPE i2s_out_spinlock = portMUX_INITIALIZER_UNLOCKED; -#define I2S_OUT_ENTER_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portENTER_CRITICAL_ISR(&i2s_out_spinlock); \ - } else { \ - portENTER_CRITICAL(&i2s_out_spinlock); \ - } \ - } while (0) -#define I2S_OUT_EXIT_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portEXIT_CRITICAL_ISR(&i2s_out_spinlock); \ - } else { \ - portEXIT_CRITICAL(&i2s_out_spinlock); \ - } \ - } while (0) -#define I2S_OUT_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_spinlock) -#define I2S_OUT_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_spinlock) - static int i2s_out_initialized = 0; static pinnum_t i2s_out_ws_pin = 255; static pinnum_t i2s_out_bck_pin = 255; static pinnum_t i2s_out_data_pin = 255; -// outer lock -static portMUX_TYPE i2s_out_pulser_spinlock = portMUX_INITIALIZER_UNLOCKED; -#define I2S_OUT_PULSER_ENTER_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portENTER_CRITICAL_ISR(&i2s_out_pulser_spinlock); \ - } else { \ - portENTER_CRITICAL(&i2s_out_pulser_spinlock); \ - } \ - } while (0) -#define I2S_OUT_PULSER_EXIT_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portEXIT_CRITICAL_ISR(&i2s_out_pulser_spinlock); \ - } else { \ - portEXIT_CRITICAL(&i2s_out_pulser_spinlock); \ - } \ - } while (0) -#define I2S_OUT_PULSER_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_pulser_spinlock) -#define I2S_OUT_PULSER_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_pulser_spinlock) - #if I2S_OUT_NUM_BITS == 16 # define DATA_SHIFT 16 #else @@ -108,11 +75,7 @@ static portMUX_TYPE i2s_out_pulser_spinlock = portMUX_INITIALIZER_UNLOCKED; // Internal functions // void IRAM_ATTR i2s_out_push_fifo(int count) { -#if 0 - uint32_t portData = ATOMIC_LOAD(&i2s_out_port_data) << DATA_SHIFT; -#else uint32_t portData = i2s_out_port_data << DATA_SHIFT; -#endif for (int i = 0; i < count; i++) { I2S0.fifo_wr = portData; } @@ -158,8 +121,6 @@ static int i2s_out_gpio_shiftout(uint32_t port_data) { } static int i2s_out_stop() { - I2S_OUT_ENTER_CRITICAL(); - // stop TX module i2s_ll_tx_stop(&I2S0); @@ -179,12 +140,6 @@ static int i2s_out_stop() { uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); // current expanded port value i2s_out_gpio_shiftout(port_data); -#if 0 - //clear pending interrupt - i2s_ll_clear_intr_status(&I2S0, i2s_ll_get_intr_status(&I2S0)); -#endif - - I2S_OUT_EXIT_CRITICAL(); return 0; } @@ -193,7 +148,6 @@ static int i2s_out_start() { return -1; } - I2S_OUT_ENTER_CRITICAL(); // Transmit recovery data to 74HC595 uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); // current expanded port value i2s_out_gpio_shiftout(port_data); @@ -208,19 +162,12 @@ static int i2s_out_start() { i2s_ll_tx_set_chan_mod(&I2S0, I2S_CHANNEL_FMT_ONLY_LEFT); i2s_ll_tx_stop_on_fifo_empty(&I2S0, true); - -#if 0 - i2s_ll_clear_intr_status(&I2S0, 0xFFFFFFFF); -#endif - i2s_ll_tx_start(&I2S0); // Wait for the first FIFO data to prevent the unintentional generation of 0 data delay_us(20); i2s_ll_tx_stop_on_fifo_empty(&I2S0, false); - I2S_OUT_EXIT_CRITICAL(); - return 0; } @@ -228,11 +175,9 @@ static int i2s_out_start() { // External funtions // void i2s_out_delay() { - I2S_OUT_PULSER_ENTER_CRITICAL(); // Depending on the timing, it may not be reflected immediately, // so wait twice as long just in case. delay_us(I2S_OUT_USEC_PER_PULSE * 2); - I2S_OUT_PULSER_EXIT_CRITICAL(); } void IRAM_ATTR i2s_out_write(pinnum_t pin, uint8_t val) { @@ -249,12 +194,8 @@ uint8_t i2s_out_read(pinnum_t pin) { return !!(port_data & (1 << pin)); } -// -// Initialize function (external function) -// int i2s_out_init(i2s_out_init_t* init_param) { if (i2s_out_initialized) { - // already initialized return -1; } @@ -370,17 +311,9 @@ int i2s_out_init(i2s_out_init_t* init_param) { i2s_ll_mclk_div_t first_div = { 2, 3, 47 }; // { N, b, a } i2s_ll_tx_set_clk(&I2S0, &first_div); - volatile void* ptr = &I2S0; - uint32_t value; - - delay_us(20); - value = *(uint32_t*)(ptr + 0xac); - i2s_ll_mclk_div_t div = { 2, 32, 16 }; // b/a = 0.5 i2s_ll_tx_set_clk(&I2S0, &div); - value = *(uint32_t*)(ptr + 0xac); - // Bit clock configuration bit in transmitter mode. // fbck = fi2s / tx_bck_div_num = (160 MHz / 5) / 2 = 16 MHz i2s_ll_tx_set_bck_div_num(&I2S0, 2); @@ -398,11 +331,111 @@ int i2s_out_init(i2s_out_init_t* init_param) { return 0; } +// Interface to step engine + static uint32_t _pulse_counts = 2; static uint32_t _dir_delay_us; -// Convert the delays from microseconds to a number of I2S frame -static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_us) { +// The key FIFO parameters are FIFO_THRESHOLD and FIFO_RELOAD +// Their sum must be less than FIFO_LENGTH (64 for ESP32). +// - FIFO_THRESHOLD is the level at which the interrupt fires. +// If it is too low, you risk FIFO underflow. Higher values +// allow more leeway for interrupt latency, but increase the +// latency between the software step generation and the appearance +// of step pulses at the driver. +// - FIFO_RELOAD is the number of entries that each ISR invocation +// pushes into the FIFO. Larger values of FIFO_RELOAD decrease +// the number of times that the ISR runs, while smaller values +// decrease the step generation latency. +// - With an I2S frame clock of 500 kHz, FIFO_THRESHOLD = 16, +// FIFO_RELOAD = 8, the step latency is about 24 us. That +// is about half of the modulation period of a laser that +// is modulated at 20 kHZ. + +#define FIFO_LENGTH (I2S_TX_DATA_NUM + 1) +#define FIFO_THRESHOLD (FIFO_LENGTH / 4) +#define FIFO_REMAINING (FIFO_LENGTH - FIFO_THRESHOLD) +#define FIFO_RELOAD 8 + +bool (*_pulse_func)(); + +static uint32_t _remaining_pulse_counts = 0; +static uint32_t _remaining_delay_counts = 0; + +static uint32_t _pulse_data; +static uint32_t _delay_counts = 40; +static uint32_t _tick_divisor; + +static void IRAM_ATTR set_timer_ticks(uint32_t ticks) { + _delay_counts = ticks / _tick_divisor; +} + +static void IRAM_ATTR start_timer() { + i2s_ll_enable_intr(&I2S0, I2S_TX_PUT_DATA_INT_ENA, 1); + i2s_ll_clear_intr_status(&I2S0, I2S_PUT_DATA_INT_CLR); +} +static void IRAM_ATTR stop_timer() { + i2s_ll_enable_intr(&I2S0, I2S_TX_PUT_DATA_INT_ENA, 0); +} + +static void IRAM_ATTR i2s_isr() { + // gpio_write(12, 1); // For debugging + + // Keeping local copies of this information speeds up the ISR + uint32_t pulse_data = _pulse_data; + uint32_t delay_data = i2s_out_port_data; + uint32_t remaining_pulse_counts = _remaining_pulse_counts; + uint32_t remaining_delay_counts = _remaining_delay_counts; + + int i = FIFO_RELOAD; + do { + if (remaining_pulse_counts) { + I2S0.fifo_wr = pulse_data; + --i; + --remaining_pulse_counts; + } else if (remaining_delay_counts) { + I2S0.fifo_wr = delay_data; + --i; + --remaining_delay_counts; + } else { + _pulse_func(); + + // Reload from variables that could have been modified by pulse_func + pulse_data = _pulse_data; + delay_data = i2s_out_port_data; + + remaining_pulse_counts = pulse_data == delay_data ? 0 : _pulse_counts; + remaining_delay_counts = _delay_counts - _pulse_counts; + } + } while (i); + + // Save the counts back to the variables + _remaining_pulse_counts = remaining_pulse_counts; + _remaining_delay_counts = remaining_delay_counts; + + // Clear the interrupt after pushing new data into the FIFO. If you clear + // it before, the interrupt will re-fire back because the FIFO is still + // below the threshold. + i2s_ll_clear_intr_status(&I2S0, I2S_PUT_DATA_INT_CLR); + + // gpio_write(12, 0); +} + +static void i2s_fifo_intr_setup() { + I2S0.fifo_conf.tx_data_num = FIFO_THRESHOLD; + esp_intr_alloc_intrstatus(ETS_I2S0_INTR_SOURCE, + ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL3, + (uint32_t)i2s_ll_get_intr_status_reg(&I2S0), + I2S_PUT_DATA_INT_CLR_M, + i2s_isr, + NULL, + NULL); +} + +static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_us, uint32_t frequency, bool (*callback)(void)) { + _pulse_func = callback; + i2s_fifo_intr_setup(); + if (pulse_us < I2S_OUT_USEC_PER_PULSE) { pulse_us = I2S_OUT_USEC_PER_PULSE; } @@ -411,6 +444,12 @@ static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_us) { } _dir_delay_us = dir_delay_us; _pulse_counts = (pulse_us + I2S_OUT_USEC_PER_PULSE - 1) / I2S_OUT_USEC_PER_PULSE; + _tick_divisor = frequency * I2S_OUT_USEC_PER_PULSE / 1000000; + + _remaining_pulse_counts = 0; + _remaining_delay_counts = 0; + + // gpio_mode(12, 0, 1, 0, 0, 0); return _pulse_counts * I2S_OUT_USEC_PER_PULSE; } @@ -426,21 +465,6 @@ static IRAM_ATTR void set_dir_pin(int pin, int level) { i2s_out_write(pin, level); } -uint32_t new_port_data; - -static IRAM_ATTR void start_step() { - new_port_data = i2s_out_port_data; -} - -static IRAM_ATTR void set_step_pin(int pin, int level) { - uint32_t bit = 1 << pin; - if (level) { - new_port_data |= bit; - } else { - new_port_data &= ~bit; - } -} - // For direction changes, we push one sample to the FIFO // and busy-wait for the delay. If the delay is short enough, // it might be possible to use the same multiple-sample trick @@ -451,22 +475,22 @@ static IRAM_ATTR void finish_dir() { delay_us(_dir_delay_us); } -// After all the desired values have been set with set_pin(), -// push _pulse_counts copies of the memory variable to the -// I2S FIFO, thus creating a pulse of the desired length. -static IRAM_ATTR void finish_step() { - if (new_port_data == i2s_out_port_data) { - return; - } - for (int i = 0; i < _pulse_counts; i++) { - I2S0.fifo_wr = new_port_data; +static void IRAM_ATTR start_step() { + _pulse_data = i2s_out_port_data; +} + +static IRAM_ATTR void set_step_pin(int pin, int level) { + uint32_t bit = 1 << pin; + if (level) { + _pulse_data |= bit; + } else { + _pulse_data &= ~bit; } - // There is no need for multiple "step off" samples since the timer will not fire - // until the next time for a pulse. - I2S0.fifo_wr = i2s_out_port_data; } -static IRAM_ATTR int start_unstep() { +static void IRAM_ATTR finish_step() {} + +static int IRAM_ATTR start_unstep() { return 1; } @@ -478,7 +502,7 @@ static uint32_t max_pulses_per_sec() { } // clang-format off -step_engine_t engine = { +step_engine_t i2s_engine = { "I2S", init_engine, init_step_pin, @@ -489,7 +513,10 @@ step_engine_t engine = { finish_step, start_unstep, finish_unstep, - max_pulses_per_sec + max_pulses_per_sec, + set_timer_ticks, + start_timer, + stop_timer }; - -REGISTER_STEP_ENGINE(I2S, &engine); +// clang-format on +REGISTER_STEP_ENGINE(I2S, &i2s_engine); diff --git a/FluidNC/esp32/rmt_engine.c b/FluidNC/esp32/rmt_engine.c index e876ab930..b5e1e1fad 100644 --- a/FluidNC/esp32/rmt_engine.c +++ b/FluidNC/esp32/rmt_engine.c @@ -6,6 +6,7 @@ #include "Driver/step_engine.h" #include "Driver/fluidnc_gpio.h" +#include "Driver/StepTimer.h" #include #include #include // IRAM_ATTR @@ -13,7 +14,8 @@ static uint32_t _pulse_delay_us; static uint32_t _dir_delay_us; -static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_delay_us) { +static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_delay_us, uint32_t frequency, bool (*callback)(void)) { + stepTimerInit(frequency, callback); _dir_delay_us = dir_delay_us; _pulse_delay_us = pulse_delay_us; return _pulse_delay_us; @@ -113,6 +115,18 @@ static uint32_t max_pulses_per_sec() { return pps; } +static void IRAM_ATTR set_timer_ticks(uint32_t ticks) { + stepTimerSetTicks(ticks); +} + +static void IRAM_ATTR start_timer() { + stepTimerStart(); +} + +static void IRAM_ATTR stop_timer() { + stepTimerStop(); +} + // clang-format off static step_engine_t engine = { "RMT", @@ -125,7 +139,10 @@ static step_engine_t engine = { finish_step, start_unstep, finish_unstep, - max_pulses_per_sec + max_pulses_per_sec, + set_timer_ticks, + start_timer, + stop_timer }; REGISTER_STEP_ENGINE(RMT, &engine); diff --git a/FluidNC/esp32/timed_engine.c b/FluidNC/esp32/timed_engine.c index 5bc74fce2..b1e6b46db 100644 --- a/FluidNC/esp32/timed_engine.c +++ b/FluidNC/esp32/timed_engine.c @@ -6,13 +6,15 @@ #include "Driver/step_engine.h" #include "Driver/fluidnc_gpio.h" #include "Driver/delay_usecs.h" +#include "Driver/StepTimer.h" #include #include // IRAM_ATTR static uint32_t _pulse_delay_us; static uint32_t _dir_delay_us; -static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_delay_us) { +static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_delay_us, uint32_t frequency, bool (*callback)(void)) { + stepTimerInit(frequency, callback); _dir_delay_us = dir_delay_us; _pulse_delay_us = pulse_delay_us; return _pulse_delay_us; @@ -32,7 +34,7 @@ static void IRAM_ATTR finish_dir() { delay_us(_dir_delay_us); } -static IRAM_ATTR void start_step() {} +static void IRAM_ATTR start_step() {} // Instead of waiting here for the step end time, we mark when the // step pulse should end, then return. The stepper code can then do @@ -42,19 +44,31 @@ static void IRAM_ATTR finish_step() { _stepPulseEndTime = usToEndTicks(_pulse_delay_us); } -static IRAM_ATTR int start_unstep() { +static int IRAM_ATTR start_unstep() { spinUntil(_stepPulseEndTime); return 0; } // This is a noop because each gpio_write() takes effect immediately, // so there is no need to commit multiple GPIO changes. -static IRAM_ATTR void finish_unstep() {} +static void IRAM_ATTR finish_unstep() {} static uint32_t max_pulses_per_sec() { return 1000000 / (2 * _pulse_delay_us); } +static void IRAM_ATTR set_timer_ticks(uint32_t ticks) { + stepTimerSetTicks(ticks); +} + +static void IRAM_ATTR start_timer() { + stepTimerStart(); +} + +static void IRAM_ATTR stop_timer() { + stepTimerStop(); +} + // clang-format off static step_engine_t engine = { "Timed", @@ -67,7 +81,10 @@ static step_engine_t engine = { finish_step, start_unstep, finish_unstep, - max_pulses_per_sec + max_pulses_per_sec, + set_timer_ticks, + start_timer, + stop_timer }; REGISTER_STEP_ENGINE(Timed, &engine); diff --git a/FluidNC/include/Driver/step_engine.h b/FluidNC/include/Driver/step_engine.h index 82431337f..f0b4e27de 100644 --- a/FluidNC/include/Driver/step_engine.h +++ b/FluidNC/include/Driver/step_engine.h @@ -9,6 +9,7 @@ #pragma once #include +#include typedef struct step_engine { const char* name; @@ -16,7 +17,7 @@ typedef struct step_engine { // Prepare the engine for use // The return value is the actual pulse delay according to the // characteristics of the engine. - uint32_t (*init)(uint32_t dir_delay_us, uint32_t pulse_delay_us); + uint32_t (*init)(uint32_t dir_delay_us, uint32_t pulse_delay_us, uint32_t frequency, bool (*fn)(void)); // Setup the step pin, returning a number to identify it. // In many cases, the return value is the same as pin, but some step @@ -52,6 +53,15 @@ typedef struct step_engine { // pulse_delay_us, and other characteristics of this stepping engine uint32_t (*max_pulses_per_sec)(); + // Set the period to the next pulse event in ticks of the stepping timer + void (*set_timer_ticks)(uint32_t ticks); + + // Start the pulse event timer + void (*start_timer)(); + + // Stop the pulse event timer + void (*stop_timer)(); + // Link to next engine in the list of registered stepping engines struct step_engine* link; } step_engine_t; diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index d3e2ef12c..eb166668d 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -36,8 +36,8 @@ namespace Machine { const EnumItem stepTypes[] = { { Stepping::TIMED, "Timed" }, { Stepping::RMT_ENGINE, "RMT" }, - { Stepping::I2S_STATIC, "I2S_static" }, - { Stepping::I2S_STREAM, "I2S_stream" }, + { Stepping::I2S_STATIC, "I2S_STATIC" }, + { Stepping::I2S_STREAM, "I2S_STREAM" }, EnumItem(Stepping::RMT_ENGINE) }; void Stepping::afterParse() { @@ -51,15 +51,10 @@ namespace Machine { log_info("Stepping:" << stepTypes[_engine].name << " Pulse:" << _pulseUsecs << "us Dsbl Delay:" << _disableDelayUsecs << "us Dir Delay:" << _directionDelayUsecs << "us Idle Delay:" << _idleMsecs << "ms"); - uint32_t actual = step_engine->init(_directionDelayUsecs, _pulseUsecs); + uint32_t actual = step_engine->init(_directionDelayUsecs, _pulseUsecs, fStepperTimer, Stepper::pulse_func); if (actual != _pulseUsecs) { log_warn("stepping/pulse_us adjusted to " << actual); } - // Prepare stepping interrupt callbacks. The one that is actually - // used is determined by timerStart() and timerStop() - - // Setup a timer for direct stepping - stepTimerInit(fStepperTimer, Stepper::pulse_func); // Register pulse_func with the I2S subsystem // This could be done via the linker. @@ -184,22 +179,20 @@ void Stepping::reset() {} void Stepping::beginLowLatency() {} void Stepping::endLowLatency() {} -// Called only from step() -void IRAM_ATTR Stepping::waitDirection() {} - // Called only from Stepper::pulse_func when a new segment is loaded // The argument is in units of ticks of the timer that generates ISRs -void IRAM_ATTR Stepping::setTimerPeriod(uint16_t timerTicks) { - stepTimerSetTicks((uint32_t)timerTicks); +void IRAM_ATTR Stepping::setTimerPeriod(uint32_t ticks) { + step_engine->set_timer_ticks((uint32_t)ticks); } // Called only from Stepper::wake_up which is not used in ISR context void Stepping::startTimer() { - stepTimerStart(); + step_engine->start_timer(); } + // Called only from Stepper::stop_stepping, used in both ISR and foreground contexts void IRAM_ATTR Stepping::stopTimer() { - stepTimerStop(); + step_engine->stop_timer(); } void Stepping::group(Configuration::HandlerBase& handler) { diff --git a/FluidNC/src/Stepping.h b/FluidNC/src/Stepping.h index fa67a8e2a..fbf7a48ab 100644 --- a/FluidNC/src/Stepping.h +++ b/FluidNC/src/Stepping.h @@ -5,7 +5,6 @@ #pragma once #include "Configuration/Configurable.h" -#include "Driver/StepTimer.h" #include "Driver/step_engine.h" namespace Machine { @@ -89,7 +88,7 @@ namespace Machine { static uint32_t maxPulsesPerSec(); // Timers - static void setTimerPeriod(uint16_t timerTicks); + static void setTimerPeriod(uint32_t timerTicks); static void startTimer(); static void stopTimer();