Stepper Driver DRV8825 doesn't hold position after movement in ESPHome setup

Hi, I’m working on an automated coffee capsule machine project controlled by an ESP32 WROOM via Home Assistant and ESPHome. I’m using a DRV8825 driver to control a NEMA17 stepper motor. The motor is responsible for positioning the coffee capsule for extraction.

I’ve successfully configured the YAML in ESPHome, and the motor moves correctly, but it doesn’t hold its position after completing a movement—it goes into what seems like “idle” or “freewheel” mode. I’m looking to keep the motor locked in place after the movement, as I need to maintain pressure during the coffee capsule positioning.

Here is my current wiring:

  • ENABLE - GPIO27
  • M0 - GPIO22
  • M1 - GPIO21
  • M2 - GPIO19
  • RESET - 3.3V
  • SLEEP - GPIO18
  • STP - GPIO26
  • DIR - GPIO13
  • FAULT - 3.3V
  • GND - GND

This is my code:

> #The substitution block has all the configurable details
> substitutions:
>   name_of_board: caffettiera
>   stepper_speed: "300 steps/s"
>   stepper_max_speed: "250 steps/s"
>   acceleration_value: "150"
>   deceleration_value: "150"
>   initial_open_position: "8000" # sets this as the open position or the farthest motor will move for open
>   initial_closed_position: "1000" # sets this as the close position or the farthest motor will move for close
>   initial_saved_position: "1000" # sets the default value if there is no saved position saved to flash
> 
> ############################################
> #YOU SHOULD NOT NEED TO EDIT BELOW THIS LINE
> ############################################
> 
> esphome:
>   name: $name_of_board
>   friendly_name: Caffettiera
>   on_boot:
>     priority: -100
>     then:
>       - stepper.report_position:
>           id: caffettiera_stepper
>           position: !lambda "return id(saved_position);"
>       - stepper.set_target:
>           id: caffettiera_stepper
>           target: !lambda "return id(saved_position);"
>       - stepper.set_speed:
>           id: caffettiera_stepper
>           speed: $stepper_speed
>       - script.execute: update_cover_position
> 
> esp32:
>   board: esp32dev
>   framework:
>     type: arduino
> 
>       
> # Enable logging
> logger:
> 
> # Enable Home Assistant API
> api:
>   encryption:
>     key: " "
>   services:
>     - service: set_stepper_target
>       variables:
>         target: int
>       then:
>         - stepper.set_target:
>             id: caffettiera_stepper
>             target: !lambda 'return target;'
>         - script.execute: record_stepper_position
>     - service: set_stepper_speed
>       variables:
>         speed: int
>       then:
>         - stepper.set_speed:
>             id: caffettiera_stepper
>             speed: !lambda 'return speed;'
>     - service: set_stepper_position
>       variables:
>         stepper_position: int
>       then:
>         - stepper.report_position:
>             id: caffettiera_stepper
>             position: !lambda "return stepper_position;"
>         - stepper.set_target:
>             id: caffettiera_stepper
>             target: !lambda "return stepper_position;"
> 
> globals:
>   - id: open_position
>     type: int
>     initial_value: $initial_open_position
>   - id: closed_position
>     type: int
>     initial_value: $initial_closed_position
>   - id: saved_position
>     type: int
>     initial_value: $initial_saved_position
>     restore_value: true
> 
> ota:
>   - platform: esphome
>     password: " "
> 
> wifi:
>   ssid: !secret wifi_ssid
>   password: !secret wifi_password
> 
>   # Enable fallback hotspot (captive portal) in case wifi connection fails
>   ap:
>     ssid: "Caffettiera Fallback Hotspot"
>     password: " "
> 
> captive_portal:
>     
> # LED exposed as binary light
> output:
>   - platform: gpio
>     pin: GPIO23
>     id: output_led
> 
> light:
>   - platform: binary
>     id: light_led
>     name: LED
>     output: output_led
> 
> # Relays exposed as switches
> switch:
>   - platform: gpio
>     pin: GPIO16
>     id: relay_1
>     name: Caldaia
> 
>   - platform: gpio
>     pin: GPIO17
>     id: relay_2
>     name: CoffePump
> 
>   - platform: gpio
>     name: "DRV8825 Enable"
>     pin: GPIO27  # Pin ENABLE
>     id: drv8825_enable
>     inverted: true  # Abilitare il driver quando il pin è LOW
>     restore_mode: ALWAYS_ON  # Sempre attivo per mantenere il motore bloccato
> 
>   - platform: gpio
>     pin: GPIO22  # M0 pin
>     name: "DRV8825 M0"
>     id: drv8825_m0
>     restore_mode: ALWAYS_ON  # Imposta M0 su HIGH all'avvio (per 1/32 step)
> 
>   - platform: gpio
>     pin: GPIO21  # M1 pin
>     name: "DRV8825 M1"
>     id: drv8825_m1
>     restore_mode: ALWAYS_ON  # Imposta M1 su HIGH all'avvio (per 1/32 step)
> 
>   - platform: gpio
>     pin: GPIO19  # M2 pin
>     name: "DRV8825 M2"
>     id: drv8825_m2
>     restore_mode: ALWAYS_ON  # Imposta M2 su HIGH all'avvio (per 1/32 step)
> 
> # Configurazione del motore stepper
> stepper:
>   - platform: a4988  # Usa DRV8825, che è compatibile
>     id: caffettiera_stepper
>     step_pin: GPIO26   # STEP pin
>     dir_pin: GPIO13    # DIR pin
>     sleep_pin: GPIO18 # SLEEP pin (collegato a GPIO18)
>     acceleration: 500
>     deceleration: 500
>     max_speed: 500  # Velocità massima in passi al secondo
> 
> cover:
>   - platform: template
>     id: caffettiera_cover
>     device_class: blind
>     name: ${name_of_board} caffettiere
>     has_position: true
>     optimistic: false
>     open_action:
>       - logger.log: "Opening"
>       - cover.template.publish:
>           id: caffettiera_cover
>           current_operation: OPENING
>       - stepper.set_target:
>           id: caffettiera_stepper
>           target: !lambda "return id(open_position);"
>       - while:
>           condition:
>             lambda: 'return id(caffettiera_stepper).current_position < id(open_position);'
>           then:
>             - script.execute: update_cover_position
>             - delay: 1000 ms
>       - script.execute: update_cover_position
>       - script.execute: record_stepper_position
>       - cover.template.publish:
>           id: caffettiera_cover
>           current_operation: IDLE
>     close_action:
>       - logger.log: "Closing"
>       - cover.template.publish:
>           id: caffettiera_cover
>           current_operation: CLOSING
>       - stepper.set_target:
>           id: caffettiera_stepper
>           target: !lambda "return id(closed_position);"
>       - while:
>           condition:
>             lambda: 'return id(caffettiera_stepper).current_position > id(closed_position);'
>           then:
>             - script.execute: update_cover_position
>             - delay: 1000 ms
>       - script.execute: update_cover_position
>       - script.execute: record_stepper_position
>       - cover.template.publish:
>           id: caffettiera_cover
>           current_operation: IDLE
>     position_action:
>       - logger.log: "Setting position"
>       - stepper.set_target:
>           id: caffettiera_stepper
>           target: !lambda 'return (float(pos) * float( float(id(open_position)) - float(id(closed_position)) )) + float(id(closed_position));'
>       - while:
>           condition:
>             lambda: 'return id(caffettiera_stepper).current_position != ((float(pos) * float( float(id(open_position)) - float(id(closed_position)) )) + float(id(closed_position)));'
>           then:
>             - script.execute: update_cover_position
>             - delay: 1000 ms
>       - script.execute: update_cover_position
>       - script.execute: record_stepper_position
>       - cover.template.publish:
>           id: caffettiera_cover
>           current_operation: IDLE
>     stop_action:
>       - logger.log: "Stopping"
>       - cover.template.publish:
>           id: caffettiera_cover
>           current_operation: IDLE
>       - stepper.set_target:
>           id: caffettiera_stepper
>           target: !lambda 'return id(caffettiera_stepper).current_position;'
>       - script.execute: update_cover_position
>       - script.execute: record_stepper_position
> 
> # Controllo dello stepper tramite una slider nell'interfaccia di Home Assistant
> number:
>   - platform: template
>     name: "Caffettiera Stepper Target Position"
>     id: target_position
>     min_value: 0
>     max_value: 6400
>     step: 10
>     unit_of_measurement: steps
>     icon: mdi:target
>     set_action:
>       # Assicurati che ENABLE e SLEEP siano attivi prima del movimento
>       - switch.turn_on: drv8825_enable
>       - stepper.set_target:
>           id: caffettiera_stepper
>           target: !lambda 'return (int(x));'         
> 
> # Opzionale: comando per tornare a 0 passi
> button:
>   - platform: template
>     name: "Caffettiera Stepper Home"
>     on_press:
>       # Attiva il driver prima del movimento verso la posizione di origine
>       - switch.turn_on: drv8825_enable
>       - stepper.set_target:
>           id: caffettiera_stepper
>           target: 0
> 
> sensor:
>   - platform: template
>     name: ${name_of_board} Current position
>     lambda: return id(caffettiera_stepper).current_position;
>     update_interval: 5s
>   - platform: wifi_signal
>     name: ${name_of_board} Wifi
>     update_interval: 60s
> binary_sensor:
>   - platform: status
>     name: ${name_of_board} Status
> script:
>   - id: update_cover_position
>     then:
>       - cover.template.publish:
>           id: caffettiera_cover
>           position: !lambda 'return float( float(id(caffettiera_stepper).current_position) - float(id(closed_position))) / float( float(id(open_position)) - float(id(closed_position)) );'
>   - id: record_stepper_position
>     then:
>       - globals.set:
>           id: saved_position
>           value: !lambda 'return id(caffettiera_stepper).current_position;'

Is there something specific I need to configure in ESPHome to keep the stepper motor holding position when it’s not moving? Or is there a setting related to current/holding torque I might be missing? Any suggestions would be greatly appreciated!

Thanks! :slight_smile:

Driver needs to have enable pin low to keep position, if you disable it the motor is not powered.
Sleep pin reduces motor current, it might hold the position even if set to sleep, you can try that.

1 Like

Also take note that when you hold it with the power on, then your driver board might overheat.
A solution might be to get a permanent magnet (PM) motor and/or use gearing.

1 Like

Hi CAROSM,

Thanks for your response! I’ve tried implementing your suggestion to keep the ENABLE pin LOW in order to maintain the stepper motor’s position. I set up the ENABLE pin as a separate switch in ESPHome and ensured that it stays active (LOW). However, the situation hasn’t changed—the stepper motor still goes into “freewheel” mode after completing its movement.

Here’s the current configuration:

  • ENABLE pin (GPIO27) is set as a switch, always active (LOW):
  - platform: gpio
    name: "DRV8825 Enable"
    pin: GPIO27  # Pin ENABLE
    id: drv8825_enable
    inverted: true  # Enable the driver when the pin is LOW
    restore_mode: ALWAYS_ON  # Always active to keep the engine blocked

(You can see my complete YAML code in the main post)

It seems I might still be missing something crucial to get the motor to hold its position. Could you clarify a bit further on how to correctly keep the ENABLE pin LOW and whether there’s another setting or approach I should take?

Thanks again for your help! :wink:

For testing, just connect enable pin to GND.
And sleep/reset pin to 3.3V

1 Like

Hi WallyR,

Thank you for the suggestion! I appreciate the note about the potential overheating of the driver board when holding the motor in position. To address this, I’m planning to install a rack and pinion system: the plunger that pushes the coffee capsule into position will be moved by a rack, and the pinion will be attached to the stepper motor.

Additionally, I’ve already installed a heatsink on the DRV8825, and I’ll be monitoring the temperatures during some tests to ensure everything stays within safe limits.

Thanks again for your valuable input!

Also, the sleep pin is there for that purpose, to lower current draw and prevent heating. But for now test it without sleep enabled.

1 Like

Also disconnect the fault pin from 3.3V

1 Like

Hi

As a first step, I disconnected the following pins

  • RESET from 3.3V
  • SLEEP from GPIO18

Then I shorted them together, leaving the ENABLE pin connected to GPIO27 on the ESP32. After testing, I noticed that the stepper motor worked correctly during its movement and is now holding its position properly once the movement is completed.

In terms of temperature, I measured 62°C on the heatsink of the DRV8825, while the motor’s temperature is 23°C when stationary.

Now, I’m wondering how to put the DRV8825 and the stepper motor into rest mode after the coffee is extracted and the motor returns to its starting position. Do you have any advice on how to implement that?

Thanks for your help! :slight_smile:

With the sleep pin of course.
You connect reset pin to 3.3V
Sleep pin to Gpio output, and write that low when you want to sleep.
The wiring scheme of DRV8825 is not same as A4988
Also disconnect fault pin from 3.3V, it’s output pin.

1 Like

Hi CAROSM,

Thank you so much for clarifying the connections for the SLEEP and RESET pins to manage the sleep mode of the DRV8825 via GPIO18 on the ESP32. Your explanation was really helpful!

I noticed that on the Pololu website, the FAULT pin is shown as usable as a 3.3V input, since it’s internally connected to the SLEEP pin. Pololu states that this setup respects the pin layout of the previous A4988 driver. However, in the second configuration shown by Pololu, which is similar to your suggestion, the FAULT pin is left disconnected.

In a few moments, I will modify the connections as per your advice. Could you also help me with updating the YAML file to implement the suspension of the DRV8825 using the SLEEP pin?

Thanks again for your valuable assistance! :robot: :heart:

DRV8825_WIRING_01
DRV8825_WIRING_02
DRV8825_FAULT_PIN_DIAGRAM

That’s valid if you have Pololu. The connections are on the carrier board.

1 Like

Hi CAROSM,

I’ve made the connections as you suggested:

  • ENABLE - GPIO27
  • M0 - GPIO22
  • M1 - GPIO21
  • M2 - GPIO19
  • RESET - 3.3V
  • SLEEP - GPIO18
  • STP - GPIO26
  • DIR - GPIO13
  • FAULT - NC
  • GND - GND

Unfortunately, the situation has reverted to how it was before, with the DRV8825 disengaging the stepper after movement. Previously, this issue was resolved by shorting RESET and SLEEP together and supplying 3.3V to the FAULT pin.

You mentioned that SLEEP can be managed via the YAML in ESPHome. Could you clarify how I can ensure that the stepper motor remains engaged after movement and how to implement the sleep function properly?

Thanks for your continued support!

First confirm that the wiring I suggested works for your board.
reset and sleep to 3.3V, Enable to gnd(or disconnected) and fault disconnected.
Is it Pololu or something else?

1 Like

Hi KAROSM, I followed your suggestion, and I’m happy to report that everything works as expected! I connected RESET and SLEEP to 3.3V, ENABLE to GND, and FAULT disconnected. The driver and motor now function correctly, with the motor locking in place after each movement. Thanks for your help!

Now, I just need to control the SLEEP pin to put the stepper and driver into sleep mode when required. Do you have any suggestions for how to best implement that in YAML of ESPhome?

You just create gpio output or gpio switch for that pin and set it defaul high. You remove sleep_pin from stepper component.
Then control the pin:
then:
- switch.turn_off: sleep_pin

or from lamda
id(your-gpio-output).turn_on();

1 Like

Hi KAROSM,

Thank you for the suggestion. I’ve set the SLEEP pin to HIGH by default in the YAML file (see code below) and kept ENABLE connected to GND as you advised. However, the energy-saving SLEEP mode doesn’t seem to hold the motor in place effectively. Only when I press “Push Stepper Target Position” or “Push Stepper Home” does the motor remain stable in position, as these actions don’t activate the SLEEP function.

Of course, I’m open to any further suggestions you might have.

Here’s the YAML configuration for reference:

#The substitution block has all the configurable details
substitutions:
  name_of_board: capsy
  stepper_speed: "300 steps/s"
  stepper_max_speed: "250 steps/s"
  acceleration_value: "150"
  deceleration_value: "150"
  initial_open_position: "8000" # sets this as the open position or the farthest motor will move for open
  initial_closed_position: "1000" # sets this as the close position or the farthest motor will move for close
  initial_saved_position: "1000" # sets the default value if there is no saved position saved to flash

############################################
#YOU SHOULD NOT NEED TO EDIT BELOW THIS LINE
############################################

esphome:
  name: $name_of_board
  on_boot:
    priority: -100
    then:   
      - stepper.report_position:
          id: push_stepper
          position: !lambda "return id(saved_position);"
      - stepper.set_target:
          id: push_stepper
          target: !lambda "return id(saved_position);"
      - stepper.set_speed:
          id: push_stepper
          speed: $stepper_speed
      - script.execute: update_cover_position

esp32:
  board: esp32dev
  framework:
    type: arduino

      
# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "your_key"
  services:
    - service: set_stepper_target
      variables:
        target: int
      then:
        - stepper.set_target:
            id: push_stepper
            target: !lambda 'return target;'
        - script.execute: record_stepper_position
    - service: set_stepper_speed
      variables:
        speed: int
      then:
        - stepper.set_speed:
            id: push_stepper
            speed: !lambda 'return speed;'
    - service: set_stepper_position
      variables:
        stepper_position: int
      then:
        - stepper.report_position:
            id: push_stepper
            position: !lambda "return stepper_position;"
        - stepper.set_target:
            id: push_stepper
            target: !lambda "return stepper_position;"

globals:
  - id: open_position
    type: int
    initial_value: $initial_open_position
  - id: closed_position
    type: int
    initial_value: $initial_closed_position
  - id: saved_position
    type: int
    initial_value: $initial_saved_position
    restore_value: true

ota:
  - platform: esphome
    password: "your_ota_password"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Capsy Fallback Hotspot"
    password: "your_ap_password"

captive_portal:
    
# LED exposed as binary light
output:
  - platform: gpio
    pin: GPIO23
    id: output_led
    
light:
  - platform: binary
    id: light_led
    name: LED
    output: output_led

# Relays exposed as switches
switch:
  - platform: gpio
    pin: GPIO16
    id: relay_1
    name: Caldaia

  - platform: gpio
    pin: GPIO17
    id: relay_2
    name: CoffePump

  - platform: gpio
    pin: GPIO18
    id: drv8825_sleep
    restore_mode: ALWAYS_ON  # Imposta SLEEP su HIGH all'avvio

  - platform: gpio
    name: "DRV8825 Enable"
    pin: GPIO27  # Pin ENABLE
    id: drv8825_enable
    inverted: true  # Abilitare il driver quando il pin è LOW
    restore_mode: ALWAYS_ON  # Sempre attivo per mantenere il motore bloccato

  - platform: gpio
    pin: GPIO22  # M0 pin
    name: "DRV8825 M0"
    id: drv8825_m0
    restore_mode: ALWAYS_ON  # Imposta M0 su HIGH all'avvio (per 1/32 step)

  - platform: gpio
    pin: GPIO21  # M1 pin
    name: "DRV8825 M1"
    id: drv8825_m1
    restore_mode: ALWAYS_ON  # Imposta M1 su HIGH all'avvio (per 1/32 step)

  - platform: gpio
    pin: GPIO19  # M2 pin
    name: "DRV8825 M2"
    id: drv8825_m2
    restore_mode: ALWAYS_ON  # Imposta M2 su HIGH all'avvio (per 1/32 step)

# Configurazione del motore stepper
stepper:
  - platform: a4988  # Usa DRV8825, che è compatibile
    id: push_stepper
    step_pin: GPIO26   # STEP pin
    dir_pin: GPIO13    # DIR pin
    acceleration: $acceleration_value
    deceleration: $deceleration_value
    max_speed: $stepper_max_speed  # Velocità massima in passi al secondo

cover:
  - platform: template
    id: cappusher
    device_class: blind
    name: ${name_of_board}
    has_position: true
    optimistic: false
    open_action:
      - logger.log: "Opening"
      - switch.turn_on: drv8825_sleep  # Attiva il pin SLEEP per il movimento
      - cover.template.publish:
          id: cappusher
          current_operation: OPENING
      - stepper.set_target:
          id: push_stepper
          target: !lambda "return id(open_position);"
      - while:
          condition:
            lambda: 'return id(push_stepper).current_position < id(open_position);'
          then:
            - script.execute: update_cover_position
            - delay: 1000 ms
      - script.execute: update_cover_position
      - script.execute: record_stepper_position
      - cover.template.publish:
          id: cappusher
          current_operation: IDLE
      - switch.turn_off: drv8825_sleep  # Disattiva il pin SLEEP al termine del movimento

    close_action:
      - logger.log: "Closing"
      - switch.turn_on: drv8825_sleep  # Attiva il pin SLEEP per il movimento
      - cover.template.publish:
          id: cappusher
          current_operation: CLOSING
      - stepper.set_target:
          id: push_stepper
          target: !lambda "return id(closed_position);"
      - while:
          condition:
            lambda: 'return id(push_stepper).current_position > id(closed_position);'
          then:
            - script.execute: update_cover_position
            - delay: 1000 ms
      - script.execute: update_cover_position
      - script.execute: record_stepper_position
      - cover.template.publish:
          id: cappusher
          current_operation: IDLE
      - switch.turn_off: drv8825_sleep  # Disattiva il pin SLEEP al termine del movimento

    position_action:
      - logger.log: "Setting position"
      - switch.turn_on: drv8825_sleep  # Attiva il pin SLEEP per il movimento
      - stepper.set_target:
          id: push_stepper
          target: !lambda 'return (float(pos) * float( float(id(open_position)) - float(id(closed_position)) )) + float(id(closed_position));'
      - while:
          condition:
            lambda: 'return id(push_stepper).current_position != ((float(pos) * float( float(id(open_position)) - float(id(closed_position)) )) + float(id(closed_position)));'
          then:
            - script.execute: update_cover_position
            - delay: 1000 ms
      - script.execute: update_cover_position
      - script.execute: record_stepper_position
      - cover.template.publish:
          id: cappusher
          current_operation: IDLE
      - switch.turn_off: drv8825_sleep  # Disattiva il pin SLEEP al termine del movimento

    stop_action:
      - logger.log: "Stopping"
      - cover.template.publish:
          id: cappusher
          current_operation: IDLE
      - stepper.set_target:
          id: push_stepper
          target: !lambda 'return id(push_stepper).current_position;'
      - script.execute: update_cover_position
      - script.execute: record_stepper_position

# Controllo dello stepper tramite una slider nell'interfaccia di Home Assistant
number:
  - platform: template
    name: "Push Stepper Target Position"
    id: target_position
    min_value: 1000
    max_value: 8000
    step: 10
    unit_of_measurement: steps
    icon: mdi:target
    set_action:
      # Assicurati che ENABLE e SLEEP siano attivi prima del movimento
      - switch.turn_on: drv8825_enable
      - switch.turn_on: drv8825_sleep  # Attiva il pin SLEEP per il movimento      
      - stepper.set_target:
          id: push_stepper
          target: !lambda 'return (int(x));'         

# Opzionale: comando per tornare a 0 passi
button:
  - platform: template
    name: "Push Stepper Home"
    on_press:
      # Attiva il driver prima del movimento verso la posizione di origine
      - switch.turn_on: drv8825_enable
      - switch.turn_on: drv8825_sleep  # Attiva il pin SLEEP per il movimento      
      - stepper.set_target:
          id: push_stepper
          target: 1000

sensor:
  - platform: template
    name: ${name_of_board} Current position
    lambda: return id(push_stepper).current_position;
    update_interval: 5s
  - platform: wifi_signal
    name: ${name_of_board} Wifi
    update_interval: 60s
binary_sensor:
  - platform: status
    name: ${name_of_board} Status
script:
  - id: update_cover_position
    then:
      - cover.template.publish:
          id: cappusher
          position: !lambda 'return float( float(id(push_stepper).current_position) - float(id(closed_position))) / float( float(id(open_position)) - float(id(closed_position)) );'
  - id: record_stepper_position
    then:
      - globals.set:
          id: saved_position
          value: !lambda 'return id(push_stepper).current_position;'

Thank you again for your help!

That’s how steppers work. If you have any external force on stepper and you power it down (or put in sleep) it doesn’t hold position.

Hi KAROSM,

I had a feeling that might be the case, but I’d read somewhere that SLEEP mode could somehow keep the motor active, albeit with reduced power.

Thanks again for clarifying!

Yes there is difference between off and sleep, but depends on the driver, how much current is given in sleep mode.
Ideally you have some place to “park” your mechanism where no external load is on motor.

1 Like