AccelStepper: tăng tốc, giảm tốc mượt cho động cơ bước NEMA 17, A4988, ESP32, về gốc, hàm hay dùng

Dùng thư viện AccelStepper cho động cơ bước NEMA 17 và A4988 trên ESP32: vì sao cần tăng tốc, cách thư viện tính khoảng cách bước, nối dây, về gốc bằng công tắc, hàm hay dùng, lỗi thường gặp.

AccelStepper là thư viện Arduino điều khiển động cơ bước có tăng tốc, giảm tốc: thay vì phát xung đều từ đầu tới cuối (dễ mất bước lúc khởi động, giật mạnh lúc dừng), thư viện tăng dần tốc độ, chạy đều, rồi giảm dần để dừng đúng vị trí. Bài này giải thích cách AccelStepper tính tốc độ (kèm mô phỏng đúng thuật toán của thư viện), cách nối NEMA 17 với driver A4988 và ESP32, chương trình chạy qua lại có về gốc bằng công tắc hành trình và nhận lệnh qua Serial, bảng hàm hay dùng và các lỗi thường gặp.

1. Vì sao cần tăng tốc, giảm tốc

Động cơ bước có mô-men giảm khi tốc độ tăng, và phải thắng quán tính của tải khi khởi động. Phát ngay xung ở tốc độ cao thì rô-to không theo kịp: động cơ rung, kêu, đứng yên hoặc mất bước. Dừng đột ngột ở tốc độ cao thì quán tính kéo tải trượt thêm vài bước. Tăng tốc, giảm tốc dần cho phép chạy nhanh hơn nhiều mà không mất bước. Cấu tạo động cơ bước, vi bước và cách chỉnh dòng driver có ở bài driver A4988, DRV8825, TMC2209.

2. Nối dây

Sơ đồ nối A4988 với ESP32: STEP vào GPIO17 TX2, DIR vào GPIO16 RX2, EN vào D21, MS1 tới MS3 và VDD vào 3V3, GND chung; công tắc gốc thường mở giữa D4 và GND
Giống chân của bài NEMA 17 với A4988.
  • STEP → GPIO17, DIR → GPIO16, EN → GPIO21 (mức thấp là cấp điện cho cuộn dây).
  • MS1, MS2, MS3 nối 3,3 V: vi bước 1/16, một vòng 200 × 16 = 3200 bước.
  • VMOT, GND nối nguồn 12 V riêng, tụ 100 µF sát chân; GND nguồn 12 V nối chung GND ESP32.
  • Công tắc hành trình thường mở giữa GPIO4 và GND, dùng điện trở kéo lên trong chip.

Cách chỉnh dòng A4988 bằng biến trở, nối dây động cơ có ở bài ESP32 điều khiển NEMA 17 với A4988.

3. AccelStepper tính tốc độ thế nào

Đồ thị tốc độ theo thời gian của AccelStepper: 9600 bước chạy hình thang tăng tốc 0,5 giây, chạy đều 3200 bước mỗi giây, giảm tốc, hết 3,49 giây; 1000 bước hình tam giác đỉnh 2531 bước mỗi giây
Quãng ngắn không kịp đạt tốc độ tối đa.

Thư viện không tính tốc độ theo thời gian mà tính khoảng cách giữa hai bước: bước đầu tiên cách c0 = 0,676 × √(2/a) giây, mỗi bước sau ngắn lại theo công thức cn = cn−1 − 2·cn−1 / (4n + 1), cho tới khi bằng khoảng cách ứng với tốc độ tối đa. Khi số bước còn lại bằng số bước cần để dừng (v² / 2a), thư viện đảo dấu n để giảm tốc. Script dưới đây làm đúng các bước đó để vẽ đồ thị và tính thời gian chạy, chạy bằng Python 3:

# PYWORLD.VN - Mô phỏng cách AccelStepper tính khoảng cách giữa các bước khi tăng tốc, chạy đều, giảm tốc (thuật toán của David Austin)
# PYWORLD.VN - c0 = 0,676 × √(2/a) × 10⁶ µs; bước sau: cn = cn−1 − 2·cn−1 / (4n + 1); chạy đều khi cn chạm 10⁶ / tốc độ tối đa
# PYWORLD.VN - Chạy: python mo_phong_accelstepper.py [số bước] [tốc độ tối đa] [gia tốc]     (mặc định 9600 3200 6400)
"""Theo đúng computeNewSpeed() của AccelStepper khi chạy một quãng từ đứng yên tới đích (không đổi đích giữa chừng)."""
import math
import sys


def mo_phong(so_buoc: int, toc_do_max: float, gia_toc: float) -> list[tuple[float, float]]:
    """Trả về danh sách (thời điểm bước, tốc độ bước/giây) cho từng bước."""
    c0 = 0.676 * math.sqrt(2.0 / gia_toc) * 1e6
    cmin = 1e6 / toc_do_max
    vi_tri, n, cn, toc_do, t, ds = 0, 0, 0.0, 0.0, 0.0, []

    def tinh():                                   # computeNewSpeed() khi chỉ chạy một chiều tới đích
        nonlocal n, cn, toc_do
        con_lai = so_buoc - vi_tri
        buoc_dung = int(toc_do * toc_do / (2.0 * gia_toc))
        if con_lai == 0 and buoc_dung <= 1:
            toc_do, n = 0.0, 0
            return 0.0
        if n > 0 and buoc_dung >= con_lai:
            n = -buoc_dung                       # bắt đầu giảm tốc
        if n == 0:
            cn = c0
        else:
            cn = max(cn - 2.0 * cn / (4.0 * n + 1), cmin)
        n += 1
        toc_do = 1e6 / cn
        return cn

    khoang = tinh()
    while khoang > 0:
        t += khoang / 1e6
        vi_tri += 1
        ds.append((t, toc_do))
        khoang = tinh()
    return ds


if __name__ == "__main__":
    so_buoc, vmax, a = (int(sys.argv[1]), float(sys.argv[2]), float(sys.argv[3])) if len(sys.argv) > 3 else (9600, 3200.0, 6400.0)
    ds = mo_phong(so_buoc, vmax, a)
    t_tang = vmax / a
    ly_thuyet = (so_buoc / vmax + t_tang) if so_buoc >= vmax * vmax / a else 2 * math.sqrt(so_buoc / a)
    print(f"{so_buoc} buoc, toc do toi da {vmax:g} buoc/s, gia toc {a:g} buoc/s2")
    print(f"Thoi gian mo phong {ds[-1][0]:.3f} s; hinh thang ly thuyet {ly_thuyet:.3f} s")
    print(f"Toc do lon nhat dat {max(v for _, v in ds):.0f} buoc/s; buoc dau cach {ds[0][0] * 1000:.2f} ms")
Bảng thời gian chạy theo tốc độ và gia tốc: 9600 bước 3200 bước mỗi giây gia tốc 6400 hết 3,49 giây, gia tốc 3200 hết 3,98 giây, tốc độ 6400 hết 2,49 giây; 1000 bước dạng tam giác 0,77 giây
Gia tốc quyết định quãng ngắn, tốc độ tối đa quyết định quãng dài.

4. Chương trình ESP32

Cài thư viện AccelStepper của Mike McCauley trong Library Manager của Arduino IDE.

// PYWORLD.VN - Động cơ bước NEMA 17 + A4988 với thư viện AccelStepper trên ESP32: tăng tốc, giảm tốc mượt, về gốc bằng công tắc hành trình
// PYWORLD.VN - Chạy qua lại 0 <-> 3 vòng; lệnh Serial: g<số bước> tới vị trí, v<bước/giây> tốc độ tối đa, a<bước/giây²> gia tốc, s dừng êm
// PYWORLD.VN - A4988: STEP -> GPIO17, DIR -> GPIO16, EN -> GPIO21 (mức thấp là bật); MS1-MS3 nối 3,3 V (1/16 vi bước); công tắc gốc GPIO4 xuống GND

#include <AccelStepper.h>

const int CHAN_STEP = 17, CHAN_DIR = 16, CHAN_EN = 21, CHAN_CONG_TAC = 4;
const long BUOC_MOI_VONG = 200L * 16;            // động cơ 1,8°, vi bước 1/16
AccelStepper dongCo(AccelStepper::DRIVER, CHAN_STEP, CHAN_DIR);
long dich = 3 * BUOC_MOI_VONG;
uint32_t lucToi = 0;
bool dangNghi = false;

void veGoc() {
  Serial.println("Ve goc: chay cham ve phia cong tac");
  dongCo.setSpeed(-800);                          // tốc độ không đổi, không tăng tốc
  while (digitalRead(CHAN_CONG_TAC) == HIGH) dongCo.runSpeed();
  dongCo.setCurrentPosition(0);                   // vị trí công tắc là 0 (lệnh này cũng đặt tốc độ về 0)
  dongCo.runToNewPosition(160);                   // lùi ra khỏi công tắc 1/20 vòng, rồi lấy đó làm gốc
  dongCo.setCurrentPosition(0);
  Serial.println("Da ve goc");
}

void xuLyLenh() {
  if (!Serial.available()) return;
  String lenh = Serial.readStringUntil('\n');
  lenh.trim();
  if (lenh.length() == 0) return;
  float so = lenh.substring(1).toFloat();
  switch (lenh[0]) {
    case 'g': dich = (long)so; dongCo.moveTo(dich); dangNghi = false; break;
    case 'v': if (so > 0) dongCo.setMaxSpeed(so); break;
    case 'a': if (so > 0) dongCo.setAcceleration(so); break;
    case 's': dongCo.stop(); break;               // giảm tốc theo gia tốc đã đặt rồi dừng, không dừng gắt
  }
  Serial.printf("Lenh %s: vi tri %ld, dich %ld\n", lenh.c_str(), dongCo.currentPosition(), dongCo.targetPosition());
}

void setup() {
  Serial.begin(115200);
  pinMode(CHAN_CONG_TAC, INPUT_PULLUP);
  dongCo.setEnablePin(CHAN_EN);
  dongCo.setPinsInverted(false, false, true);     // chân EN của A4988 tích cực mức thấp
  dongCo.enableOutputs();
  dongCo.setMinPulseWidth(2);                     // µs; A4988 cần xung STEP tối thiểu 1 µs
  dongCo.setMaxSpeed(3200);                       // bước/giây = 1 vòng/giây
  dongCo.setAcceleration(6400);                   // bước/giây²: đạt tốc độ tối đa sau 0,5 giây
  veGoc();
  dongCo.moveTo(dich);
}

void loop() {
  xuLyLenh();
  dongCo.run();                                   // gọi càng thường xuyên càng tốt, không dùng delay()
  if (dongCo.distanceToGo() == 0 && !dangNghi) {
    dangNghi = true;
    lucToi = millis();
    Serial.printf("Toi vi tri %ld\n", dongCo.currentPosition());
  }
  if (dangNghi && millis() - lucToi >= 1000 && (dich == 0 || dich == 3 * BUOC_MOI_VONG)) {
    dich = (dich == 0) ? 3 * BUOC_MOI_VONG : 0;   // chạy qua lại sau 1 giây nghỉ
    dongCo.moveTo(dich);
    dangNghi = false;
  }
}
  • Không dùng delay() trong loop(): run() chỉ đi một bước khi đến lúc, phải được gọi liên tục. Một lệnh delay(100) là động cơ khựng lại.
  • In Serial ít thôi: in mỗi vòng lặp làm chậm run(), động cơ chạy không đều. Chương trình chỉ in khi tới vị trí hoặc khi nhận lệnh.
  • Về gốc: chạy chậm tốc độ không đổi (runSpeed) về phía công tắc, gặp công tắc thì đặt vị trí 0, lùi ra một đoạn rồi lấy làm gốc.
  • stop() dừng êm theo gia tốc đã đặt; cần dừng khẩn thì cắt chân EN bằng phần cứng.
  • Lệnh Serial: g9600 tới vị trí 9600, v6400 đổi tốc độ tối đa, a3200 đổi gia tốc, s dừng.
Bảng hàm AccelStepper: setMaxSpeed, setAcceleration, moveTo, move, run, runSpeed với setSpeed, runToNewPosition chặn chương trình, stop, distanceToGo, currentPosition, setCurrentPosition
Chỉ runToNewPosition chặn chương trình.

Động cơ 28BYJ-48 với mạch ULN2003 cũng dùng được AccelStepper với kiểu nửa bước: AccelStepper dc(AccelStepper::HALF4WIRE, IN1, IN3, IN2, IN4) (chú ý thứ tự IN1, IN3, IN2, IN4); xem bài 28BYJ-48 với ULN2003.

5. Lỗi hay gặp

Hiện tượngNguyên nhânCách xử lý
Động cơ không quay, chỉ rungThiếu setMaxSpeed (mặc định rất chậm) hoặc thiếu setAccelerationĐặt cả hai trước moveTo
Quay chậm hơn tốc độ đã đặtrun() không được gọi đủ nhanh: delay, in Serial nhiềuBỏ delay, giảm in; việc nặng chuyển sang nhân thứ hai của ESP32
Mất bước khi khởi độngGia tốc quá lớn, dòng driver thấpGiảm gia tốc; chỉnh dòng A4988
Chạy ngược chiềuĐảo cặp cuộn dây hoặc quy ước chiềusetPinsInverted(true, false, true) hoặc đảo một cặp dây
Driver nóng khi đứng yênCuộn dây giữ dòng liên tụcdisableOutputs() khi không cần giữ vị trí (mất mô-men giữ)
Về gốc đi luôn không dừngCông tắc nối sai, chiều về gốc ngượcĐọc thử digitalRead của công tắc; đổi dấu tốc độ về gốc

6. Xem thêm

Sản phẩm dùng trong bài: ESP32 DevKit V1 30 chân.

Bài cùng cụm chủ đề: động cơ bước kêu, rung, mất bước; điều khiển 16 servo bằng PCA9685.


Tin tức liên quan

Đưa ESP32 và relay 4 kênh vào Home Assistant bằng MQTT discovery, không viết YAML
Đưa ESP32 và relay 4 kênh vào Home Assistant bằng MQTT discovery, không viết YAML

ESP32 tự gửi tin cấu hình MQTT discovery, Home Assistant tạo 4 công tắc relay và cảm biến Wi-Fi; Mosquitto, tài khoản, di chúc, retain và bảng xử lý lỗi.

Máy cho cá ăn tự động bằng ESP32: servo SG90, DS3231 giữ giờ khi mất điện, đặt lịch trên web
Máy cho cá ăn tự động bằng ESP32: servo SG90, DS3231 giữ giờ khi mất điện, đặt lịch trên web

Làm máy cho cá ăn tự động bằng ESP32: servo SG90 quay đĩa có lỗ, DS3231 giữ giờ khi mất điện, đồng bộ giờ Internet, trang web đặt 4 giờ cho ăn, không cho ăn lặp sau mất điện.

Hướng dẫn điều khiển relay bằng điện thoại qua Bluetooth với ESP32
Hướng dẫn điều khiển relay bằng điện thoại qua Bluetooth với ESP32

Hướng dẫn dùng Bluetooth Classic của ESP32 trên Arduino IDE: ghép đôi với điện thoại Android, gửi nhận chữ bằng BluetoothSerial và điều khiển module relay bằng lệnh ON, OFF.

Hướng dẫn làm cân điện tử với ESP32, HX711 và load cell trên Arduino IDE
Hướng dẫn làm cân điện tử với ESP32, HX711 và load cell trên Arduino IDE

Hướng dẫn nối load cell với module HX711 và ESP32 DevKit V1, gá load cell đúng cách, hiệu chuẩn bằng vật đã biết khối lượng và viết chương trình cân trên Arduino IDE.

Đo chất lượng không khí trong phòng bằng ESP32: CO2 với SCD40, bụi PM2.5 với PMS5003, AQI giờ
Đo chất lượng không khí trong phòng bằng ESP32: CO2 với SCD40, bụi PM2.5 với PMS5003, AQI giờ

Làm máy đo không khí trong phòng bằng ESP32: CO2 thật với SCD40, bụi PM2.5 với PMS5003, OLED, tính AQI giờ theo VN_AQI và NowCast, biết lúc nào mở cửa, lúc nào bật máy lọc.

Đèn LED nháy theo nhạc với ESP32: micro INMP441, FFT tự viết, dải WS2812B, bắt nhịp trống
Đèn LED nháy theo nhạc với ESP32: micro INMP441, FFT tự viết, dải WS2812B, bắt nhịp trống

Làm đèn LED nháy theo nhạc với ESP32: micro INMP441 thu tiếng, FFT 512 điểm chia 6 dải tần, bắt nhịp trống tính BPM, dải WS2812B với ba chế độ phổ, nhịp và VU.

Cấu hình Wi-Fi cho ESP32 bằng điện thoại, không nạp lại code: tự làm trang cài đặt như WiFiManager
Cấu hình Wi-Fi cho ESP32 bằng điện thoại, không nạp lại code: tự làm trang cài đặt như WiFiManager

ESP32 tự phát mạng Wi-Fi khi chưa có cấu hình, điện thoại vào là bật trang chọn mạng, lưu bằng Preferences; chỉ dùng thư viện có sẵn, giữ BOOT 3 giây để xóa.

Đế ra chân ESP32 30 chân: cấp nguồn DC 6,5–16 V, hàng chân G V S và jumper chọn 5 V hay 3,3 V
Đế ra chân ESP32 30 chân: cấp nguồn DC 6,5–16 V, hàng chân G V S và jumper chọn 5 V hay 3,3 V

Cách dùng đế ra chân ESP32 30 chân: cấu tạo, cắm ESP32 đúng chiều, cấp nguồn jack DC 6,5–16 V và tính nhiệt IC ổn áp, hàng G V S, jumper 5V/3.3V, ví dụ relay và nút nhấn.


Bình luận
Đã thêm vào giỏ hàng