diff --git a/FluidNC/esp32/esp32/rmt_engine.c b/FluidNC/esp32/esp32/rmt_engine.c deleted file mode 100644 index fad6774708..0000000000 --- a/FluidNC/esp32/esp32/rmt_engine.c +++ /dev/null @@ -1,149 +0,0 @@ -// 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 ESP32 RMT hardware to time step pulses, thus avoiding -// the need to wait for the end of step pulses. - -#include "Driver/step_engine.h" -#include "Driver/fluidnc_gpio.h" -#include "Driver/StepTimer.h" -#include -#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, 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; -} - -// Allocate an RMT channel and attach the step_pin GPIO to it, -// setting the timing according to dir_delay_us and pulse_delay_us. -// Return the index of that RMT channel which will be presented to -// set_step_pin() later. -static uint32_t init_step_pin(pinnum_t step_pin, bool step_inverted) { - static rmt_channel_t next_RMT_chan_num = RMT_CHANNEL_0; - if (next_RMT_chan_num == RMT_CHANNEL_MAX) { - return -1; - } - rmt_channel_t rmt_chan_num = next_RMT_chan_num; - next_RMT_chan_num = (rmt_channel_t)((int)(next_RMT_chan_num) + 1); - - rmt_config_t rmtConfig = { .rmt_mode = RMT_MODE_TX, - .channel = rmt_chan_num, - .gpio_num = (gpio_num_t)step_pin, - .clk_div = 20, - .mem_block_num = 2, - .flags = 0, - .tx_config = { - .carrier_freq_hz = 0, - .carrier_level = RMT_CARRIER_LEVEL_LOW, - .idle_level = step_inverted ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW, - .carrier_duty_percent = 50, -#if SOC_RMT_SUPPORT_TX_LOOP_COUNT - .loop_count = 1, -#endif - .carrier_en = false, - .loop_en = false, - .idle_output_en = true, - } }; - - rmt_item32_t rmtItem[2]; - rmtItem[0].duration0 = _dir_delay_us ? _dir_delay_us * 4 : 1; - rmtItem[0].duration1 = _pulse_delay_us * 4; - rmtItem[1].duration0 = 0; - rmtItem[1].duration1 = 0; - - rmtItem[0].level0 = rmtConfig.tx_config.idle_level; - rmtItem[0].level1 = !rmtConfig.tx_config.idle_level; - rmt_config(&rmtConfig); - rmt_fill_tx_items(rmtConfig.channel, &rmtItem[0], rmtConfig.mem_block_num, 0); - return (int)rmt_chan_num; -} - -// The direction pin is a GPIO that is accessed in the usual way -static void IRAM_ATTR set_dir_pin(pinnum_t pin, bool level) { - gpio_write(pin, level); -} - -// The direction delay is handled by the RMT pulser -static void IRAM_ATTR finish_dir() {} - -// No need for any common setup before setting step pins -static void IRAM_ATTR start_step() {} - -// Restart the RMT which has already been configured -// for the desired pulse length, polarity, and direction delay -static void IRAM_ATTR set_step_pin(pinnum_t pin, bool level) { -#ifdef CONFIG_IDF_TARGET_ESP32 - RMT.conf_ch[pin].conf1.mem_rd_rst = 1; - RMT.conf_ch[pin].conf1.mem_rd_rst = 0; - RMT.conf_ch[pin].conf1.tx_start = 1; -#endif -#ifdef CONFIG_IDF_TARGET_ESP32S3 - RMT.chnconf0[pin].mem_rd_rst_n = 1; - RMT.chnconf0[pin].mem_rd_rst_n = 0; - RMT.chnconf0[pin].tx_start_n = 1; -#endif -} - -// This is a noop because the RMT channels do everything -static void IRAM_ATTR finish_step() {} - -// This is a noop because the RMT channels take care -// of the pulse trailing edges. -// Return 1 (true) to tell Stepping.cpp that it can -// skip the rest of the step pin deassertion process -static bool IRAM_ATTR start_unstep() { - return 1; -} - -// This is a noop and will not be called because start_unstep() -// returns 1 -static void IRAM_ATTR finish_unstep() {} - -// Possible speedup: If the direction delay were done explicitly -// instead of baking it into the RMT timing, we might be able to -// get more pulses per second, since direction changes are infrequent -// and thus do not need to be applied to every pulse -static uint32_t max_pulses_per_sec() { - uint32_t pps = 1000000 / (2 * _pulse_delay_us + _dir_delay_us); - 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", - init_engine, - init_step_pin, - set_dir_pin, - finish_dir, - start_step, - set_step_pin, - finish_step, - start_unstep, - finish_unstep, - max_pulses_per_sec, - set_timer_ticks, - start_timer, - stop_timer, - NULL -}; - -REGISTER_STEP_ENGINE(RMT, &engine); diff --git a/FluidNC/esp32/esp32s3/Platform.h b/FluidNC/esp32/esp32s3/Platform.h index adf0162f32..b4ddcc14ba 100644 --- a/FluidNC/esp32/esp32s3/Platform.h +++ b/FluidNC/esp32/esp32s3/Platform.h @@ -10,7 +10,7 @@ // The number that we support, regardless of how many the chip has #define MAX_N_DACS 0 -#define MAX_N_RMT 0 +#define MAX_N_RMT 4 #define MAX_N_I2SO 1 #define MAX_N_SPI 1 #define MAX_N_SDCARD 1 diff --git a/FluidNC/esp32/rmt_engine.c b/FluidNC/esp32/rmt_engine.c new file mode 100644 index 0000000000..0649a09fca --- /dev/null +++ b/FluidNC/esp32/rmt_engine.c @@ -0,0 +1,201 @@ +// 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 ESP32 RMT hardware to time step pulses, thus avoiding +// the need to wait for the end of step pulses. +// +// **implementation brief**: +// - Uses RMT internal SRAM (not DMA) +// - Pre-fills 2 RMT items (1 pulse + 1 terminator) at init time +// - Runtime just triggers RMT with direct register access (reset pointer + start) +// - No driver API, no dynamic buffers - simple, fast, reliable + +#include "Driver/step_engine.h" +#include "Driver/fluidnc_gpio.h" +#include "Driver/StepTimer.h" +#include "Platform.h" +#include +#include +#include // IRAM_ATTR + +// Include RMT hardware structures for direct register access +#ifdef CONFIG_IDF_TARGET_ESP32 + #include +#endif +#ifdef CONFIG_IDF_TARGET_ESP32S3 + #include + #include // Low-level RMT API for S3 +#endif + +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, 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; +} + +// Allocate an RMT channel and attach the step_pin GPIO to it, +// setting the timing according to dir_delay_us and pulse_delay_us. +// Return the index of that RMT channel which will be presented to +// set_step_pin() later. +// +// **initialization brief**: +// - clk_div = 20: APB clock (80MHz) / 20 = 4MHz → 0.25us per RMT tick +// - mem_block_num = 1: Use 1 memory block (64 items, we only need 2) +// - Fill 2 items: [0] = pulse pattern, [1] = terminator (all zeros) +// - The pulse pattern stays in RMT internal SRAM permanently +// - Every trigger replays the same pattern - no runtime updates needed +static uint32_t init_step_pin(pinnum_t step_pin, bool step_inverted) { + static rmt_channel_t next_RMT_chan_num = RMT_CHANNEL_0; + if (next_RMT_chan_num >= MAX_N_RMT) { + return -1; + } + rmt_channel_t rmt_chan_num = next_RMT_chan_num; + next_RMT_chan_num = (rmt_channel_t)((int)(next_RMT_chan_num) + 1); + + rmt_config_t rmtConfig = { + .rmt_mode = RMT_MODE_TX, + .channel = rmt_chan_num, + .gpio_num = (gpio_num_t)step_pin, + .clk_div = 20, // 4MHz RMT clock (0.25us per tick) + .mem_block_num = 3, // 3 memory blocks = 192 RMT items (reduce FIFO reload interrupts) + .flags = 0, + .tx_config = { + .carrier_freq_hz = 0, + .carrier_level = RMT_CARRIER_LEVEL_LOW, + .idle_level = step_inverted ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW, + .carrier_duty_percent = 50, +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + .loop_count = 1, +#endif + .carrier_en = false, + .loop_en = false, + .idle_output_en = true, + } + }; + + // Prepare 2-item pulse pattern + rmt_item32_t rmtItem[2]; + + // Item 0: Pulse pattern + // duration0 = idle time (low level), duration1 = pulse time (high level) + // With clk_div=20, each tick = 0.25us, so durations are microseconds * 4 + rmtItem[0].duration0 = _dir_delay_us ? (_dir_delay_us * 4) : 4; // Min 4 ticks = 1us + rmtItem[0].duration1 = _pulse_delay_us * 4; + rmtItem[0].level0 = rmtConfig.tx_config.idle_level; + rmtItem[0].level1 = !rmtConfig.tx_config.idle_level; + + // Item 1: Terminator (all zeros = end of transmission) + rmtItem[1].duration0 = 0; + rmtItem[1].duration1 = 0; + + // Configure RMT hardware + rmt_config(&rmtConfig); + + // Copy pulse pattern to RMT internal SRAM (stays there permanently) + rmt_fill_tx_items(rmtConfig.channel, &rmtItem[0], 2, 0); + + return (int)rmt_chan_num; +} + +// The direction pin is a GPIO that is accessed in the usual way +static void IRAM_ATTR set_dir_pin(pinnum_t pin, bool level) { + gpio_write(pin, level); +} + +// The direction delay is handled by the RMT pulser +static void IRAM_ATTR finish_dir() {} + +// No need for any common setup before setting step pins +static void IRAM_ATTR start_step() {} + +// Restart the RMT which has already been configured +// for the desired pulse length, polarity, and direction delay +// +// **pulse trigger** (ultra-fast, ~20-50ns overhead): +// 1. Reset memory read pointer to position 0 +// 2. Start RMT transmission +// 3. RMT hardware reads items from internal SRAM and generates pulse +// 4. CPU returns immediately - pulse generation is 100% hardware +static void IRAM_ATTR set_step_pin(pinnum_t pin, bool level) { +#ifdef CONFIG_IDF_TARGET_ESP32 + // ESP32 classic: Direct register access + RMT.conf_ch[pin].conf1.mem_rd_rst = 1; + RMT.conf_ch[pin].conf1.mem_rd_rst = 0; + RMT.conf_ch[pin].conf1.tx_start = 1; +#endif + +#ifdef CONFIG_IDF_TARGET_ESP32S3 + // ESP32-S3: Use low-level API (cleaner, forward-compatible) + rmt_ll_tx_reset_pointer(&RMT, (rmt_channel_t)pin); + rmt_ll_tx_start(&RMT, (rmt_channel_t)pin); +#endif +} + +// This is a noop because the RMT channels do everything +static void IRAM_ATTR finish_step() {} + +// This is a noop because the RMT channels take care +// of the pulse trailing edges. +// Return 1 (true) to tell Stepping.cpp that it can +// skip the rest of the step pin deassertion process +static bool IRAM_ATTR start_unstep() { + return 1; +} + +// This is a noop and will not be called because start_unstep() +// returns 1 +static void IRAM_ATTR finish_unstep() {} + +// Maximum pulses per second based on configured pulse timing +// +// With 4MHz RMT clock (clk_div=20): +// - Theoretical max depends on pulse width + dir delay +// - Example: 5us pulse + 2us delay = 7us total → ~143kHz +// - ESP32-S3 should achieve similar or better performance +// - Higher clock precision (0.25us vs 1us) reduces quantization jitter +// +// Note: Actual max rate also depends on: +// - ISR execution time (~1-2us per interrupt) +// - Stepper trajectory calculation overhead +// - Number of axes moving simultaneously +static uint32_t max_pulses_per_sec() { + uint32_t pps = 1000000 / (2 * _pulse_delay_us + _dir_delay_us); + 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", + init_engine, + init_step_pin, + set_dir_pin, + finish_dir, + start_step, + set_step_pin, + finish_step, + start_unstep, + finish_unstep, + max_pulses_per_sec, + set_timer_ticks, + start_timer, + stop_timer, + NULL +}; + +REGISTER_STEP_ENGINE(RMT, &engine); diff --git a/FluidNC/src/Config.h b/FluidNC/src/Config.h index d5912b15e7..962a229100 100644 --- a/FluidNC/src/Config.h +++ b/FluidNC/src/Config.h @@ -103,7 +103,7 @@ const int REPORT_WCO_REFRESH_IDLE_COUNT = 10; // (2-255) Must be less than or e // NOTE: Changing this value also changes the execution time of a segment in the step segment buffer. // When increasing this value, this stores less overall time in the segment buffer and vice versa. Make // certain the step segment buffer is increased/decreased to account for these changes. -const int ACCELERATION_TICKS_PER_SECOND = 100; +const int ACCELERATION_TICKS_PER_SECOND = 200; // Minimum planner junction speed. Sets the default minimum junction speed the planner plans to at // every buffer block junction, except for starting from rest and end of the buffer, which are always