ESPHome - 28BYJ-48 motors with A4988 driver

Hi,
I found an other thread (from 2019) but it has become too long so was very difficult to navigate/read, hence starting a new one here.

I am trying to make a motorized window blinds using a 12V 28BYJ-48 stepper motor, Wemos D1 mini and A4988 driver.

The wiring is done as shown below with few exceptions:

  • Enable, MS1, MS2 & MS3 pins are not connected
  • Reset and Sleep are connected together and attached to D3 pin on the Wemos board
  • DIR is connected to D1 pin on the Wemos board
  • STEP is connected to D1 pin on the Wemos board

The ESPHome Config file is as follows:

esphome:
  name: d1-test
  platform: ESP8266
  board: d1_mini

# Enable logging
logger:

ota:
  password: "PASS"

wifi:
  ssid: !secret ssid
  password: !secret ssid_password
  fast_connect: True

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "D1-Test Fallback Hotspot"
    password: "PASS"


# Enable Home Assistant API
api:
  password: 'PASS'
  services:
    - service: control_stepper
      variables:
        target: float
      then:
        - stepper.set_target:
            id: my_stepper
            target: !lambda "return target;"
        - cover.template.publish:
            id: blinds
            position: !lambda "return 1 - ((target) / id(stepper_steps));"
            
            
  
globals:
  - id: stepper_steps
    type: int
    initial_value: '11600'
  - id: stepper_position
    type: float
    initial_value: '0'
  - id: stepper_state
    type: int
    initial_value: '0'

binary_sensor:
  - platform: gpio
    pin: D8
    name: "PM_Button"
    filters:
    on_press:
      then:
        - lambda: |
            if (id(stepper_state) == 0) {
              //shade is stopped
              if (((id(stepper_position) < (id(stepper_steps) / 2)) || (id(stepper_position) == id(stepper_steps))) && (id(stepper_position) > 0)) {
                id(blinds).open();
              } else {
                id(blinds).close();
              }
            } else {
              //shade is moving
              id(blinds).stop();
            }

cover:
  - platform: template
    name: "Blinds"
    id: blinds
    open_action:
      - globals.set:
          id: stepper_state
          value: '1'
      - stepper.set_target:
          id: my_stepper
          target: 0
      - delay: !lambda "return (id(my_stepper).current_position / id(stepper_steps)) * 30000;"
      - globals.set:
          id: stepper_position
          value: !lambda "return id(my_stepper).target_position;"
      - globals.set:
          id: stepper_state
          value: '0'
    close_action:
      - globals.set:
          id: stepper_state
          value: '1'
      - stepper.set_target:
          id: my_stepper
          target: !lambda "return id(stepper_steps);"
      - delay: !lambda "return (1 - (id(my_stepper).current_position / id(stepper_steps))) * 30000;"
      - globals.set:
          id: stepper_position
          value: !lambda "return id(my_stepper).target_position;"
      - globals.set:
          id: stepper_state
          value: '0'
    stop_action:
      - stepper.set_target:
          id: my_stepper
          target: !lambda "return id(my_stepper).current_position;" 
      - globals.set:
          id: stepper_position
          value: !lambda "return id(my_stepper).current_position;"
      - globals.set:
          id: stepper_state
          value: '0'
      - cover.template.publish:
          id: blinds
          position: !lambda "return 1 - (id(stepper_position) / id(stepper_steps));"
    position_action:
      - stepper.set_target:
          id: my_stepper
          target: !lambda "return id(stepper_steps) * (1 - pos);"
    optimistic: true
    assumed_state: true
    has_position: true
    
stepper:
  - platform: a4988
    id: my_stepper
    dir_pin: D1
    step_pin: D2
    max_speed: 500 steps/s

    # Optional:
    sleep_pin: D3
    acceleration: inf
    deceleration: inf

The motor seems to be working in general but running into few issues:

  • the motor just seems to turn one way (randomly) and not sure if I am operating it wrong
  • The torque does not seem enough, see the link to see the bead slipping even when trying to rotate a very blind.

https://youtu.be/ki9a8pFgZF0

If anyone has any pointers for troubleshooting then please let me know.
Thanks!

Try another pin for your binary sensor. Do not use D8

@laca75tn Thanks!

I switched to D5 pin on Wemos and grounded it (instead of D8) in wiring and changed configuration.
But it did not improve the torque on the motor and blinds will not rotate.

It must be something wrong with my setup as lots of other users have been to deploy it successfully (see YT videos) using similar set up. I will give it some break and then start from scratch in next few days in case I am doing something obvious stupid :grinning:

A request to others who may have used a stepper motor, please post the link to your SKU.

Here are the two stepper motors I tried for this project:
a) 5V version
b) 12V version

Just trying to eliminate the possibility that I may be using an incompatible SKU for this project.
Thanks.

I recommend you watching this:

Motorize and Automate your Blinds for $10! (WiFi) - YouTube specially at 2:30

Ok this one tested my whole knowledge of electrical engineering :grinning:

I was under the impression that a 12V 28BYJ-48 will have more torque than the 5V version but seems like I may be better off using a 12V supply on a 5V version with A4988 driver in short burst. And this is actually true as the motor shaft literally need move just about 90 degrees (1/4th rotation) to get blinds to either open or close.

I will give this one a try and report updates!!

Another question, is it fair to assume that barring any manufacturing quality variances all 5V 28BYJ-48 should have same torque?

Just trying to eliminate OEM specific factor since I searched for the the cheapest deal on Amazon/eBay/Ali and ordered.

From all information that I have read/watch on the 28BYJ-48, I would say that all behave the same. I have used different brands on different projects and they are the same. Of course, that’s a generalization. :slight_smile:

If you don’t have this fixed by now I would suggest limiting the stepper speed to 300 steps and setting your accel and decell to arounld 150.

1 Like

Hiya,

I’m trying to do exactly the same thing. Here’s where I ended up:

  • Hardware: D1Mini (ESP8266), A4988 motor driver, 28BYJ-48 motor (5V version, hacked to be bipolar), voltage converter (set to convert 12V to 5V), 12V power supply.
  • Mounting: I don’t have a 3D printer but I LOVE instamorph and use it for everything, and it’s perfect for this. You can get knockoff versions, they’re basically the same. Heat it up in a pot of hot water, and you get a gob of almost molten plastic, into which you can sink the motor to make a mounting. It hardens solid in < 5min and I found it was completely perfect.
  • Coupling: I have two blinds and they required different couplings because one has a very stiff tilt shaft, the other moves easily
    • For the easy moving one, I used a metal shaft coupler from Amazon. Not perfect as I had to grind down the tilt rod slightly but very strong.
    • For the stiff one, I left the worm gear mechanism in place and just removed the spool, replacing it with a LEGO 24-tooth Technic gear. I fitted a 16-tooth gear to the motor shaft, and mounted the motor sideways so that the gears meshed. This gear ratio plus the worm gear mechanism gave me enough reduction to allow the 28BYJ to tilt these very stiff blinds.
  • Connections (sorry I don’t know how to do the fancy diagrams!):
    • Control
      • D1Mini D2 — A4988 Sleep — A4988 Reset (see below!)
      • D1Mini D3 — A4933 Step
      • D1Mini D4 — A4988 Direction
    • Motor
      • A4988 1B pin 11 — Motor orange
      • A4988 1A pin 12 — Motor pink
      • A4988 2A pin 13 — Motor yellow
      • A4988 2B pin 14 — Motor blue
    • Motor power
      • 12VAC — Voltage converter IN+ — A4988 VMOT
      • Voltage converter IN- — A4988 GND pin 15
    • Logic power
      • Voltage converter OUT+ (5V) — D1Mini 5V — A4988 VDD
      • D1Mini GND — A4988 GND pin 9 — Voltage converter OUT-
  • ESPHome config:
    esphome:
      name: blinds-tilt3
      platform: ESP8266
      board: d1_mini
      esp8266_restore_from_flash: true
      on_boot:
        priority: -100
        then:
          - stepper.report_position:
              id: blind_stepper
              position: !lambda "return id(saved_position);"
          - stepper.set_target:
              id: blind_stepper
              target: !lambda "return id(saved_position);"
          - stepper.set_speed:
              id: blind_stepper
              speed: 200 steps/s
          - script.execute: update_cover_position
    
    logger:
    
    ota:
      password: "foo" # set by ESPHome - use that value!
    
    wifi:
      ssid: "foo"
      password: "foo"
      reboot_timeout: 2min
    
      # Enable fallback hotspot (captive portal) in case wifi connection fails
      ap:
        ssid: "Blinds-Tilt3 Fallback Hotspot"
        password: "foo"
    
    captive_portal:
    
    
    # Enable Home Assistant API
    api:
      services:
        - service: set_stepper_target
          variables:
            target: int
          then:
            - stepper.set_target:
                id: blind_stepper
                target: !lambda 'return target;'
            - script.execute: record_stepper_position
        - service: set_stepper_speed
          variables:
            speed: int
          then:
            - stepper.set_speed:
                id: blind_stepper
                speed: !lambda 'return speed;'
        - service: set_stepper_position
          variables:
            stepper_position: int
          then:
            - stepper.report_position:
                id: blind_stepper
                position: !lambda "return stepper_position;"
            - stepper.set_target:
                id: blind_stepper
                target: !lambda "return stepper_position;"
    
    globals:
      - id: open_position
        type: int
        initial_value: '8000'
      - id: closed_position
        type: int
        initial_value: '1000'
      - id: saved_position
        type: int
        initial_value: '1000'
        restore_value: true
    
    stepper:
      - platform: a4988
        id: blind_stepper
        dir_pin: D4
        step_pin: D3
        sleep_pin: D2
        max_speed: 250 steps/s
        acceleration: 200
        deceleration: 200
    
    cover:
      - platform: template
        id: "blind_cover"
        device_class: blind
        name: "Blinds"
        has_position: true
        optimistic: false
        open_action:
          - logger.log: "Opening"
          - cover.template.publish:
              id: blind_cover
              current_operation: OPENING
          - stepper.set_target:
              id: blind_stepper
              target: !lambda "return id(open_position);"
          - while:
              condition:
                lambda: 'return id(blind_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: blind_cover
              current_operation: IDLE
        close_action:
          - logger.log: "Closing"
          - cover.template.publish:
              id: blind_cover
              current_operation: CLOSING
          - stepper.set_target:
              id: blind_stepper
              target: !lambda "return id(closed_position);"
          - while:
              condition:
                lambda: 'return id(blind_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: blind_cover
              current_operation: IDLE
        position_action:
          - logger.log: "Setting position"
          - stepper.set_target:
              id: blind_stepper
              target: !lambda 'return (float(pos) * float( float(id(open_position)) - float(id(closed_position)) )) + float(id(closed_position));'
          - while:
              condition:
                lambda: 'return id(blind_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: blind_cover
              current_operation: IDLE
        stop_action:
          - logger.log: "Stopping"
          - cover.template.publish:
              id: blind_cover
              current_operation: IDLE
          - stepper.set_target:
              id: blind_stepper
              target: !lambda 'return id(blind_stepper).current_position;'
          - script.execute: update_cover_position
          - script.execute: record_stepper_position
    
    sensor:
      - platform: template
        name: "Current stepper position"
        lambda: return id(blind_stepper).current_position;
        update_interval: 5s
    
    script:
      - id: update_cover_position
        then:
          - cover.template.publish:
              id: blind_cover
              position: !lambda 'return float( float(id(blind_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(blind_stepper).current_position;'
    

Key learnings:

  • Many tutorials tell you to bridge the reset and sleep pins but don’t say to provide a sleep signal. I tried various combinations before realising that I needed to connect a pin on my microcontroller to both the sleep and reset pins, and configure it in ESPHome as the sleep pin.
  • D4 is the built in LED on the D1 Mini so if you use it for the direction signal to the motor controller, it will light up when the motor is going in one direction and be off when it goes in the other direction!
  • It’s pretty vital to have the ESPHome remember the current position of the motor, because if not, and you have a power cut, when the power comes back you might break your blinds/motor/mount by trying to close already-closed blinds.
  • Having a bit of acceleration and deceleration in the ESPHome config gives you extra torque when you need it.