diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py
index 7a6b4fa52..6a59bbfb9 100644
--- a/klippy/extras/tmc.py
+++ b/klippy/extras/tmc.py
@@ -224,6 +224,8 @@ class TMCCommandHelper:
         self.stepper_enable = self.printer.load_object(config, "stepper_enable")
         self.printer.register_event_handler("stepper:sync_mcu_position",
                                             self._handle_sync_mcu_pos)
+        self.printer.register_event_handler("klippy:mcu_identify",
+                                            self._handle_mcu_identify)
         self.printer.register_event_handler("klippy:connect",
                                             self._handle_connect)
         # Set microstep config options
@@ -345,6 +347,12 @@ class TMCCommandHelper:
             self.echeck_helper.stop_checks()
         except self.printer.command_error as e:
             self.printer.invoke_shutdown(str(e))
+    def _handle_mcu_identify(self):
+        # Lookup stepper object
+        force_move = self.printer.lookup_object("force_move")
+        self.stepper = force_move.lookup_stepper(self.stepper_name)
+        # Note pulse duration and step_both_edge optimizations available
+        self.stepper.setup_default_pulse_duration(.000000100, True)
     def _handle_stepper_enable(self, print_time, is_enable):
         if is_enable:
             cb = (lambda ev: self._do_enable(print_time))
@@ -352,9 +360,10 @@ class TMCCommandHelper:
             cb = (lambda ev: self._do_disable(print_time))
         self.printer.get_reactor().register_callback(cb)
     def _handle_connect(self):
-        # Lookup stepper object
-        force_move = self.printer.lookup_object("force_move")
-        self.stepper = force_move.lookup_stepper(self.stepper_name)
+        # Check if using step on both edges optimization
+        pulse_duration, step_both_edge = self.stepper.get_pulse_duration()
+        if step_both_edge:
+            self.fields.set_field("dedge", 1)
         # Check for soft stepper enable/disable
         enable_line = self.stepper_enable.lookup_enable(self.stepper_name)
         enable_line.register_state_callback(self._handle_stepper_enable)
diff --git a/klippy/extras/tmc2660.py b/klippy/extras/tmc2660.py
index f55f6f015..c0d8fa9b9 100644
--- a/klippy/extras/tmc2660.py
+++ b/klippy/extras/tmc2660.py
@@ -98,7 +98,6 @@ SignedFields = ["sgt"]
 
 FieldFormatters = dict(tmc2130.FieldFormatters)
 FieldFormatters.update({
-    "dedge": (lambda v: "1(Both Edges Active!)" if v else ""),
     "chm": (lambda v: "1(constant toff)" if v else "0(spreadCycle)"),
     "vsense": (lambda v: "1(165mV)" if v else "0(305mV)"),
     "sdoff": (lambda v: "1(Step/Dir disabled!)" if v else ""),
diff --git a/klippy/stepper.py b/klippy/stepper.py
index c5f813827..2e6655eff 100644
--- a/klippy/stepper.py
+++ b/klippy/stepper.py
@@ -32,6 +32,7 @@ class MCU_stepper:
                 "Stepper dir pin must be on same mcu as step pin")
         self._dir_pin = dir_pin_params['pin']
         self._invert_dir = dir_pin_params['invert']
+        self._step_both_edge = self._req_step_both_edge = False
         self._mcu_position_offset = 0.
         self._reset_cmd_tag = self._get_position_cmd = None
         self._active_callbacks = []
@@ -54,6 +55,12 @@ class MCU_stepper:
     def units_in_radians(self):
         # Returns true if distances are in radians instead of millimeters
         return self._units_in_radians
+    def get_pulse_duration(self):
+        return self._step_pulse_duration, self._step_both_edge
+    def setup_default_pulse_duration(self, pulse_duration, step_both_edge):
+        if self._step_pulse_duration is None:
+            self._step_pulse_duration = pulse_duration
+        self._req_step_both_edge = step_both_edge
     def setup_itersolve(self, alloc_func, *params):
         ffi_main, ffi_lib = chelper.get_ffi()
         sk = ffi_main.gc(getattr(ffi_lib, alloc_func)(*params), ffi_lib.free)
@@ -61,11 +68,18 @@ class MCU_stepper:
     def _build_config(self):
         if self._step_pulse_duration is None:
             self._step_pulse_duration = .000002
+        invert_step = self._invert_step
+        sbe = int(self._mcu.get_constants().get('STEPPER_BOTH_EDGE', '0'))
+        if self._req_step_both_edge and sbe:
+            # Enable stepper optimized step on both edges
+            self._step_both_edge = True
+            self._step_pulse_duration = 0.
+            invert_step = -1
         step_pulse_ticks = self._mcu.seconds_to_clock(self._step_pulse_duration)
         self._mcu.add_config_cmd(
             "config_stepper oid=%d step_pin=%s dir_pin=%s invert_step=%d"
             " step_pulse_ticks=%u" % (self._oid, self._step_pin, self._dir_pin,
-                                      self._invert_step, step_pulse_ticks))
+                                      invert_step, step_pulse_ticks))
         self._mcu.add_config_cmd("reset_step_clock oid=%d clock=0"
                                  % (self._oid,), on_restart=True)
         step_cmd_tag = self._mcu.lookup_command_tag(
diff --git a/src/Kconfig b/src/Kconfig
index 43ab2bc66..e36094e67 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -108,6 +108,9 @@ config HAVE_STRICT_TIMING
 config HAVE_CHIPID
     bool
     default n
+config HAVE_STEPPER_BOTH_EDGE
+    bool
+    default n
 
 config INLINE_STEPPER_HACK
     # Enables gcc to inline stepper_event() into the main timer irq handler
diff --git a/src/atsam/Kconfig b/src/atsam/Kconfig
index a3d8566d7..f9d13c341 100644
--- a/src/atsam/Kconfig
+++ b/src/atsam/Kconfig
@@ -13,6 +13,7 @@ config ATSAM_SELECT
     select HAVE_GPIO_BITBANGING
     select HAVE_STRICT_TIMING
     select HAVE_CHIPID
+    select HAVE_STEPPER_BOTH_EDGE
 
 config BOARD_DIRECTORY
     string
diff --git a/src/atsamd/Kconfig b/src/atsamd/Kconfig
index 5684ff6fb..57c99c9f0 100644
--- a/src/atsamd/Kconfig
+++ b/src/atsamd/Kconfig
@@ -13,6 +13,7 @@ config ATSAMD_SELECT
     select HAVE_GPIO_BITBANGING
     select HAVE_STRICT_TIMING
     select HAVE_CHIPID
+    select HAVE_STEPPER_BOTH_EDGE
 
 config HAVE_SERCOM
     depends on HAVE_GPIO_I2C || HAVE_GPIO_SPI
diff --git a/src/lpc176x/Kconfig b/src/lpc176x/Kconfig
index 81a98b057..f40c3943e 100644
--- a/src/lpc176x/Kconfig
+++ b/src/lpc176x/Kconfig
@@ -13,6 +13,7 @@ config LPC_SELECT
     select HAVE_STRICT_TIMING
     select HAVE_CHIPID
     select HAVE_GPIO_HARD_PWM
+    select HAVE_STEPPER_BOTH_EDGE
 
 config BOARD_DIRECTORY
     string
diff --git a/src/rp2040/Kconfig b/src/rp2040/Kconfig
index 1682fa9ab..b509bc04b 100644
--- a/src/rp2040/Kconfig
+++ b/src/rp2040/Kconfig
@@ -12,6 +12,7 @@ config RP2040_SELECT
     select HAVE_STRICT_TIMING
     select HAVE_CHIPID
     select HAVE_GPIO_HARD_PWM
+    select HAVE_STEPPER_BOTH_EDGE
 
 config BOARD_DIRECTORY
     string
diff --git a/src/stepper.c b/src/stepper.c
index 5d69d9df2..f81d7041b 100644
--- a/src/stepper.c
+++ b/src/stepper.c
@@ -14,9 +14,18 @@
 #include "stepper.h" // stepper_event
 #include "trsync.h" // trsync_add_signal
 
-#if CONFIG_INLINE_STEPPER_HACK && CONFIG_MACH_AVR
+#if CONFIG_INLINE_STEPPER_HACK && CONFIG_HAVE_STEPPER_BOTH_EDGE
+ #define HAVE_SINGLE_SCHEDULE 1
+ #define HAVE_EDGE_OPTIMIZATION 1
+ #define HAVE_AVR_OPTIMIZATION 0
+ DECL_CONSTANT("STEPPER_BOTH_EDGE", 1);
+#elif CONFIG_INLINE_STEPPER_HACK && CONFIG_MACH_AVR
+ #define HAVE_SINGLE_SCHEDULE 1
+ #define HAVE_EDGE_OPTIMIZATION 0
  #define HAVE_AVR_OPTIMIZATION 1
 #else
+ #define HAVE_SINGLE_SCHEDULE 0
+ #define HAVE_EDGE_OPTIMIZATION 0
  #define HAVE_AVR_OPTIMIZATION 0
 #endif
 
@@ -66,9 +75,10 @@ stepper_load_next(struct stepper *s, uint32_t min_next_time)
     struct stepper_move *m = container_of(mn, struct stepper_move, node);
     s->add = m->add;
     s->interval = m->interval + m->add;
-    if (HAVE_AVR_OPTIMIZATION && s->flags & SF_SINGLE_SCHED) {
+    if (HAVE_SINGLE_SCHEDULE && s->flags & SF_SINGLE_SCHED) {
         s->time.waketime += m->interval;
-        s->flags = m->add ? s->flags | SF_HAVE_ADD : s->flags & ~SF_HAVE_ADD;
+        if (HAVE_AVR_OPTIMIZATION)
+            s->flags = m->add ? s->flags|SF_HAVE_ADD : s->flags & ~SF_HAVE_ADD;
         s->count = m->count;
     } else {
         // On faster mcus, it is necessary to schedule unstep events
@@ -97,6 +107,22 @@ stepper_load_next(struct stepper *s, uint32_t min_next_time)
     return SF_RESCHEDULE;
 }
 
+// Optimized step function to step on each step pin edge
+uint_fast8_t
+stepper_event_edge(struct timer *t)
+{
+    struct stepper *s = container_of(t, struct stepper, time);
+    gpio_out_toggle_noirq(s->step_pin);
+    uint32_t count = s->count - 1;
+    if (likely(count)) {
+        s->count = count;
+        s->time.waketime += s->interval;
+        s->interval += s->add;
+        return SF_RESCHEDULE;
+    }
+    return stepper_load_next(s, 0);
+}
+
 #define AVR_STEP_INSNS 40 // minimum instructions between step gpio pulses
 
 // AVR optimized step function
@@ -150,6 +176,8 @@ reschedule_min:
 uint_fast8_t
 stepper_event(struct timer *t)
 {
+    if (HAVE_EDGE_OPTIMIZATION)
+        return stepper_event_edge(t);
     if (HAVE_AVR_OPTIMIZATION)
         return stepper_event_avr(t);
     return stepper_event_full(t);
@@ -159,13 +187,19 @@ void
 command_config_stepper(uint32_t *args)
 {
     struct stepper *s = oid_alloc(args[0], command_config_stepper, sizeof(*s));
-    s->flags = args[3] ? SF_INVERT_STEP : 0;
+    int_fast8_t invert_step = args[3];
+    s->flags = invert_step > 0 ? SF_INVERT_STEP : 0;
     s->step_pin = gpio_out_setup(args[1], s->flags & SF_INVERT_STEP);
     s->dir_pin = gpio_out_setup(args[2], 0);
     s->position = -POSITION_BIAS;
     s->step_pulse_ticks = args[4];
     move_queue_setup(&s->mq, sizeof(struct stepper_move));
-    if (HAVE_AVR_OPTIMIZATION) {
+    if (HAVE_EDGE_OPTIMIZATION) {
+        if (!s->step_pulse_ticks && invert_step < 0)
+            s->flags |= SF_SINGLE_SCHED;
+        else
+            s->time.func = stepper_event_full;
+    } else if (HAVE_AVR_OPTIMIZATION) {
         if (s->step_pulse_ticks <= AVR_STEP_INSNS)
             s->flags |= SF_SINGLE_SCHED;
         else
@@ -252,7 +286,7 @@ stepper_get_position(struct stepper *s)
 {
     uint32_t position = s->position;
     // If stepper is mid-move, subtract out steps not yet taken
-    if (HAVE_AVR_OPTIMIZATION && s->flags & SF_SINGLE_SCHED)
+    if (HAVE_SINGLE_SCHEDULE && s->flags & SF_SINGLE_SCHED)
         position -= s->count;
     else
         position -= s->count / 2;
@@ -286,7 +320,8 @@ stepper_stop(struct trsync_signal *tss, uint8_t reason)
     s->count = 0;
     s->flags = (s->flags & (SF_INVERT_STEP|SF_SINGLE_SCHED)) | SF_NEED_RESET;
     gpio_out_write(s->dir_pin, 0);
-    gpio_out_write(s->step_pin, s->flags & SF_INVERT_STEP);
+    if (!(HAVE_EDGE_OPTIMIZATION && s->flags & SF_SINGLE_SCHED))
+        gpio_out_write(s->step_pin, s->flags & SF_INVERT_STEP);
     while (!move_queue_empty(&s->mq)) {
         struct move_node *mn = move_queue_pop(&s->mq);
         struct stepper_move *m = container_of(mn, struct stepper_move, node);
diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig
index 3fef9a162..c14abc6db 100644
--- a/src/stm32/Kconfig
+++ b/src/stm32/Kconfig
@@ -13,6 +13,7 @@ config STM32_SELECT
     select HAVE_GPIO_BITBANGING if !MACH_STM32F031
     select HAVE_STRICT_TIMING
     select HAVE_CHIPID
+    select HAVE_STEPPER_BOTH_EDGE
 
 config BOARD_DIRECTORY
     string