ESP32 điều khiển động cơ không chổi than qua ESC: xung 1000–2000 µs, hiệu chuẩn dải ga, thử an toàn

Dùng ESP32 phát xung ga 1000–2000 µs bằng LEDC để điều khiển ESC và động cơ không chổi than: nối dây với pin LiPo, hiệu chuẩn dải ga, tăng ga từ từ, khóa động cơ và an toàn khi thử.

Động cơ không chổi than của flycam không nối thẳng vào vi điều khiển được: nó cần dòng vài chục ampe, đổi chiều theo ba pha đúng thời điểm. Việc đó do ESC (bộ điều tốc điện tử) đảm nhận. Mạch điều khiển chỉ gửi cho ESC một tín hiệu "ga" cho biết cần quay nhanh hay chậm.

Bài viết hướng dẫn dùng ESP32 phát tín hiệu ga chuẩn 1000–2000 µs bằng bộ LEDC, nối dây an toàn với ESC và pin LiPo, hiệu chuẩn dải ga, tăng ga từ từ và khóa động cơ bằng lệnh trên Serial Monitor. Chương trình này dùng được để thử động cơ trên bàn, đo lực đẩy, và là phần xuất lệnh động cơ khi tự làm mạch điều khiển bay.

1. ESC nhận lệnh thế nào

ESC loại nhỏ nối với động cơ không chổi than bằng ba đầu cos xanh, phía ESC có hai dây nguồn đỏ đen và cáp tín hiệu ba sợi đầu cắm đen
ESC nối với động cơ không chổi than qua ba dây pha; cáp ba sợi bên phải là cáp tín hiệu. Ảnh: Shatton8111, CC BY-SA 3.0, Wikimedia Commons, đã cắt, thu nhỏ và làm mờ nhãn trên ESC.

Một ESC thường có ba nhóm dây:

  • Hai dây nguồn (đỏ, đen) nối với pin LiPo.
  • Ba dây pha nối với ba dây của động cơ. Thứ tự không quan trọng; đảo hai dây bất kỳ thì động cơ đổi chiều quay.
  • Cáp tín hiệu: dây trắng hoặc vàng là tín hiệu, dây đen hoặc nâu là GND. Dây đỏ ở giữa (nếu có) là ngõ ra 5 V của mạch ổn áp BEC trong ESC, dùng để cấp điện cho bộ thu trên máy bay mô hình.

Tín hiệu ga kiểu cũ nhất, ESC nào cũng nhận, là xung PWM như servo: mỗi chu kỳ có một xung mức cao, rộng 1000 µs là ga thấp nhất, 2000 µs là ga cao nhất. Mạch điều khiển bay hiện nay chủ yếu dùng giao thức số DShot (đã giới thiệu ở bài động cơ, ESC và cánh quạt cùng loạt); bài này dùng xung PWM vì đơn giản và hợp với mọi ESC.

2. Linh kiện cần chuẩn bị

  • ESP32 DevKit V1 30 chân.
  • Một ESC 20–30 A nhận xung PWM và động cơ không chổi than cỡ phù hợp (ví dụ loại 2212 cho khung 450 mm).
  • Pin LiPo đúng số cell ESC và động cơ cho phép (ví dụ 3S), đầu nối pin cùng loại với ESC.
  • Giá kẹp động cơ chắc chắn, hoặc kẹp động cơ vào khung flycam đã tháo cánh.
  • Dây cắm test board để nối cáp tín hiệu vào ESP32.

3. Sơ đồ nối dây

Sơ đồ nối: pin LiPo 3S cấp nguồn cho ESC, ba dây pha A B C từ ESC sang động cơ không chổi than, dây tín hiệu trắng của ESC vào D25 của ESP32, dây đen vào GND, dây đỏ 5 V của ESC để trống không nối khi ESP32 cắm USB
Hình minh họa: ESP32 chỉ nối hai dây với ESC; pin LiPo chỉ cấp điện cho ESC.
  • Dây tín hiệu của ESC nối GPIO25, dây GND nối GND của ESP32. Thiếu dây GND chung thì ESC không đọc được xung.
  • Không nối dây đỏ 5 V của ESC khi ESP32 đang cắm USB: hai nguồn 5 V nối với nhau có thể làm hỏng cổng USB hoặc mạch ổn áp. Bọc đầu dây đỏ lại, hoặc rút chân đỏ khỏi đầu cắm.
  • Khi ESP32 chạy độc lập không cắm USB, có thể lấy 5 V của BEC cấp vào chân VIN. ESC loại không có BEC (thường ghi OPTO) thì cáp tín hiệu chỉ có hai sợi.
  • Mức cao 3,3 V của ESP32 đủ để phần lớn ESC nhận. Nếu ESC không phản ứng, thêm mạch chuyển mức 3,3 V sang 5 V cho dây tín hiệu.

4. Xung PWM và bộ LEDC

Ba dạng xung chu kỳ 20 ms: 1000 µs ứng với duty 3276 trên 65535, 1500 µs ứng với 4915, 2000 µs ứng với 6553; công thức duty bằng độ rộng xung nhân 65535 chia 20000
Hình minh họa: độ rộng xung và giá trị duty ESP32 ghi vào LEDC.

Bộ LEDC của ESP32 tạo xung PWM bằng phần cứng, không tốn thời gian CPU. Chương trình đặt tần số 50 Hz (chu kỳ 20 000 µs) và độ phân giải 16 bit, nên giá trị duty đi từ 0 tới 65 535:

duty = độ rộng xung (µs) × 65535 / 20000

  • Mỗi bước duty ứng với khoảng 0,3 µs, tức dải ga 1000 µs được chia thành khoảng 3 270 mức, mịn hơn nhiều so với khả năng phân biệt của ESC.
  • Nhiều ESC đời mới nhận xung lặp nhanh hơn (tới khoảng 490 Hz), giúp động cơ phản ứng sớm hơn. Chỉ cần sửa TAN_SO_PWM, chương trình tự tính lại chu kỳ và duty. Chu kỳ phải dài hơn 2000 µs, nên không đặt quá 490 Hz.

5. Hiệu chuẩn dải ga

Năm bước hiệu chuẩn ESC: tháo cánh, gõ c để ESP32 xuất 2000 µs, cắm pin cho ESC nghe bíp, gõ x để xuất 1000 µs, nghe bíp xác nhận
Hình minh họa: thứ tự hiệu chuẩn bằng lệnh c và x.

Mỗi mạch phát xung có sai lệch nhỏ, mỗi ESC xuất xưởng với dải ga hơi khác nhau. Hiệu chuẩn để ESC ghi nhớ đúng mức 1000 µs và 2000 µs của ESP32; khi dùng nhiều ESC (bốn động cơ của flycam), hiệu chuẩn cả bốn để chúng bắt đầu quay ở cùng một mức ga.

  1. Tháo cánh quạt. Rút pin ESC, cắm USB cho ESP32.
  2. Gõ c: ESP32 xuất 2000 µs.
  3. Cắm pin cho ESC. ESC phát chuỗi bíp báo đã nhận mức cao.
  4. Gõ x: ESP32 xuất 1000 µs. ESC bíp xác nhận đã lưu dải ga.
  5. Rút pin, cắm lại để dùng bình thường.

Kiểu tiếng bíp và thời gian chờ khác nhau giữa các loại ESC, nên đọc tài liệu của ESC đang dùng. Chương trình chỉ cho vào chế độ hiệu chuẩn khi động cơ đang khóa.

6. Chương trình

Mở Serial Monitor ở 115200 baud, kiểu xuống dòng "New Line", rồi gõ lệnh:

Lệnh Serial
LệnhTác dụng
aMở khóa (chỉ được khi đang khóa và ga ở mức thấp nhất)
t1200Đặt ga 1200 µs; ga thật tăng hoặc giảm dần tới giá trị này
dKhóa: về 1000 µs ngay lập tức
c, xBắt đầu và kết thúc hiệu chuẩn (mục 5)
/*
  PYWORLD.VN - ESP32 điều khiển động cơ không chổi than qua ESC bằng xung PWM chuẩn 1000-2000 us
  Dây tín hiệu ESC (trắng hoặc vàng) = GPIO25, dây âm ESC (đen hoặc nâu) = GND; không nối dây đỏ BEC khi cắm USB
  Lệnh Serial: a mở khóa, d khóa, t<us> đặt ga (ví dụ t1200), c hiệu chuẩn ESC, x kết thúc hiệu chuẩn
*/

#define CHAN_ESC 25
#define TAN_SO_PWM 50            // Hz; ESC đời mới nhận được tới khoảng 490 Hz
#define DO_PHAN_GIAI 16          // bit
#define GA_THAP_NHAT 1000        // us
#define GA_CAO_NHAT 2000         // us
#define GIOI_HAN_GA 1400         // us: ga tối đa cho phép khi thử trên bàn, tăng dần khi đã chắc chắn
#define TOC_DO_TANG 400          // us mỗi giây: ga thay đổi từ từ, không giật

const uint32_t CHU_KY_US = 1000000UL / TAN_SO_PWM;
const uint32_t DUTY_TOI_DA = (1UL << DO_PHAN_GIAI) - 1;

enum TrangThai { KHOA, MO_KHOA, HIEU_CHUAN };
TrangThai trangThai = KHOA;
float gaHienTai = GA_THAP_NHAT;   // us đang xuất ra
int gaMucTieu = GA_THAP_NHAT;     // us người dùng đặt
unsigned long lanCapNhatTruoc = 0;
unsigned long lanInTruoc = 0;

// Đổi độ rộng xung (us) sang giá trị duty của LEDC
uint32_t usSangDuty(uint32_t us) {
  return us * DUTY_TOI_DA / CHU_KY_US;
}

void xuatXung(uint32_t us) {
  ledcWrite(CHAN_ESC, usSangDuty(us));
}

void inTrangThai() {
  const char *ten = (trangThai == KHOA) ? "khoa" : (trangThai == MO_KHOA) ? "da mo khoa" : "hieu chuan";
  Serial.printf("Ga: %4d us | duty %5u/%u | %s\n", (int)gaHienTai, (unsigned)usSangDuty((uint32_t)gaHienTai),
                (unsigned)DUTY_TOI_DA, ten);
}

void xuLyLenh() {
  if (!Serial.available()) {
    return;
  }
  String lenh = Serial.readStringUntil('\n');
  lenh.trim();
  if (lenh.length() == 0) {
    return;
  }
  char ma = lenh.charAt(0);
  if (ma == 'd') {                        // Khóa: về ga thấp nhất ngay
    trangThai = KHOA;
    gaMucTieu = GA_THAP_NHAT;
    gaHienTai = GA_THAP_NHAT;
    xuatXung(GA_THAP_NHAT);
    Serial.println("Da khoa dong co");
  } else if (ma == 'a') {
    if (trangThai == KHOA && gaMucTieu == GA_THAP_NHAT) {
      trangThai = MO_KHOA;
      Serial.println("Da mo khoa: go t<us> de dat ga, d de khoa");
    } else {
      Serial.println("Chi mo khoa khi dang khoa va ga o muc thap nhat");
    }
  } else if (ma == 't') {
    int us = lenh.substring(1).toInt();
    if (trangThai != MO_KHOA) {
      Serial.println("Dang khoa: go a de mo khoa truoc");
    } else if (us < GA_THAP_NHAT || us > GIOI_HAN_GA) {
      Serial.printf("Ga phai trong khoang %d-%d us\n", GA_THAP_NHAT, GIOI_HAN_GA);
    } else {
      gaMucTieu = us;
      Serial.printf("Ga muc tieu: %d us\n", gaMucTieu);
    }
  } else if (ma == 'c') {
    if (trangThai != KHOA) {
      Serial.println("Khoa dong co (d) truoc khi hieu chuan");
      return;
    }
    trangThai = HIEU_CHUAN;
    gaHienTai = GA_CAO_NHAT;
    xuatXung(GA_CAO_NHAT);
    Serial.println("HIEU CHUAN: da thao canh? Dang xuat 2000 us.");
    Serial.println("Bay gio cam pin cho ESC, doi tieng bip bao muc cao roi go x");
  } else if (ma == 'x' && trangThai == HIEU_CHUAN) {
    gaHienTai = GA_THAP_NHAT;
    xuatXung(GA_THAP_NHAT);
    trangThai = KHOA;
    Serial.println("Da xuat 1000 us: doi tieng bip xac nhan, hieu chuan xong");
  } else {
    Serial.println("Lenh: a mo khoa | d khoa | t<us> dat ga | c hieu chuan | x ket thuc hieu chuan");
  }
}

void setup() {
  Serial.begin(115200);
  // Xuất ga thấp nhất ngay khi khởi động để ESC không quay và nhận ra tín hiệu
  ledcAttach(CHAN_ESC, TAN_SO_PWM, DO_PHAN_GIAI);
  xuatXung(GA_THAP_NHAT);
  delay(100);
  Serial.println();
  Serial.printf("ESC: %d Hz, chu ky %u us, ga %d-%d us, gioi han thu %d us\n", TAN_SO_PWM, (unsigned)CHU_KY_US,
                GA_THAP_NHAT, GA_CAO_NHAT, GIOI_HAN_GA);
  Serial.println("Dang khoa. Go a de mo khoa (THAO CANH khi thu tren ban)");
  lanCapNhatTruoc = millis();
}

void loop() {
  xuLyLenh();
  unsigned long bayGio = millis();
  float dt = (bayGio - lanCapNhatTruoc) / 1000.0;
  lanCapNhatTruoc = bayGio;

  if (trangThai == MO_KHOA) {
    // Đưa ga hiện tại tiến dần về ga mục tiêu với tốc độ giới hạn
    float buoc = TOC_DO_TANG * dt;
    if (gaHienTai < gaMucTieu) {
      gaHienTai = (gaHienTai + buoc > gaMucTieu) ? gaMucTieu : gaHienTai + buoc;
    } else if (gaHienTai > gaMucTieu) {
      gaHienTai = (gaHienTai - buoc < gaMucTieu) ? gaMucTieu : gaHienTai - buoc;
    }
    xuatXung((uint32_t)gaHienTai);
  } else if (trangThai == KHOA) {
    gaHienTai = GA_THAP_NHAT;
    xuatXung(GA_THAP_NHAT);
  }

  if (bayGio - lanInTruoc >= 500) {
    lanInTruoc = bayGio;
    if (trangThai == MO_KHOA) {
      inTrangThai();
    }
  }
  delay(5);
}

7. Giải thích các phần chính

  • ledcAttach(CHAN_ESC, TAN_SO_PWM, DO_PHAN_GIAI) gắn chân GPIO25 vào bộ LEDC (cú pháp của gói ESP32 phiên bản 3), sau đó ledcWrite(CHAN_ESC, duty) đổi độ rộng xung.
  • Ngay khi khởi động, chương trình xuất 1000 µs. ESC chỉ sẵn sàng (bíp báo sẵn sàng) khi thấy mức ga thấp nhất; nhờ đó động cơ không thể tự quay lúc vừa cắm pin.
  • Ba trạng thái KHOA, MO_KHOA, HIEU_CHUAN quyết định lệnh nào được chấp nhận. Lệnh t bị từ chối khi đang khóa hoặc khi vượt GIOI_HAN_GA.
  • GIOI_HAN_GA đặt 1400 µs cho lần thử đầu trên bàn. Khi đã chắc chắn về giá kẹp và nguồn, tăng dần giới hạn này.
Đồ thị ga theo thời gian: sau lệnh a và t1200 ga tăng dần lên 1200 µs trong nửa giây, t1400 tăng tiếp lên 1400, t1100 giảm dần, lệnh d đưa ga về 1000 µs ngay
Hình minh họa: ga xuất ra theo chuỗi lệnh ví dụ, tính đúng theo thuật toán của chương trình.

Trong loop(), ga hiện tại tiến về ga đặt với tốc độ tối đa 400 µs mỗi giây. Ga không nhảy đột ngột nên động cơ không giật, dòng điện không vọt lên, và nếu gõ nhầm giá trị thì vẫn kịp gõ d. Lệnh khóa thì ngược lại: đưa ga về 1000 µs ngay, không chờ giảm dần.

8. Kết quả

Hình minh họa Serial Monitor: dòng cấu hình 50 Hz chu kỳ 20000 us, mở khóa, đặt ga 1200 us, các dòng trạng thái ga và duty, lệnh t1500 bị từ chối vì vượt giới hạn 1400, khóa động cơ
Hình minh họa: một lượt thử từ mở khóa tới khóa động cơ.

Khi cắm pin, ESC thường phát vài tiếng bíp theo số cell, rồi một tiếng báo sẵn sàng. Sau lệnh a và t1100 đến t1200, động cơ bắt đầu quay chậm; mức ga bắt đầu quay của mỗi ESC khác nhau một chút (thường khoảng 1050–1150 µs).

9. An toàn khi thử động cơ

  • Luôn tháo cánh khi nối dây, hiệu chuẩn và thử chương trình lần đầu. Động cơ flycam quay hàng chục nghìn vòng mỗi phút; cánh quạt có thể gây thương tích nặng.
  • Khi gắn cánh để đo lực đẩy: giá phải bắt vít chắc vào bàn, người đứng ngoài mặt phẳng quay của cánh, đeo kính bảo hộ, tăng ga từng bước nhỏ.
  • Cắm pin cho ESC sau cùng, rút pin trước khi sửa bất kỳ dây nào. Cách dừng nhanh nhất là gõ d hoặc rút pin.
  • Pin LiPo không để chập hai cực, không dùng pin phồng, sạc bằng bộ sạc cân bằng đúng số cell.

10. Xử lý lỗi thường gặp

Lỗi thường gặp
Hiện tượngNguyên nhân thường gặpCách xử lý
ESC bíp đều mãi, không báo sẵn sàngKhông có xung (sai chân, thiếu GND chung) hoặc ga lúc cắm pin cao hơn mức thấp nhất đã lưuKiểm tra dây tín hiệu và GND; hiệu chuẩn lại
Động cơ rung, giật, không quay trònMột dây pha tiếp xúc kém hoặc đứtKiểm tra mối hàn, đầu nối của ba dây pha
Động cơ quay ngược chiềuThứ tự dây phaRút pin, đảo hai dây pha bất kỳ
Ga đặt 1100 µs mà động cơ chưa quayMức bắt đầu quay của ESC cao hơnTăng dần ga; hiệu chuẩn để các ESC đồng đều
ESP32 khởi động lại khi động cơ chạyDây 5 V của ESC nối chung với USB; nhiễu từ dây nguồn động cơBỏ dây đỏ 5 V; đi dây tín hiệu xa dây pha, dây GND ngắn
ESC rất nóngĐộng cơ, cánh quá tải so với dòng định mức của ESCDùng cánh nhỏ hơn hoặc ESC dòng lớn hơn

11. Xem thêm

Cùng loạt: động cơ flycam, ESC, cánh quạt và lực đẩy; pin LiPo cho flycam; ESP32 làm mạch điều khiển bay cho flycam mini.


Tin tức liên quan

ESP32 điều khiển servo SG90 bằng PWM, không cần thư viện: quét góc, biến trở, hiệu chỉnh
ESP32 điều khiển servo SG90 bằng PWM, không cần thư viện: quét góc, biến trở, hiệu chỉnh

Hướng dẫn điều khiển servo SG90 bằng ESP32 với bộ PWM LEDC 50 Hz có sẵn: xung 0,5–2,4 ms, sơ đồ nối, quét góc, vặn biến trở, hiệu chỉnh hai đầu hành trình và thí nghiệm STEM.

Hướng dẫn đo điện áp 16 bit bằng module ADS1115 với ESP32
Hướng dẫn đo điện áp 16 bit bằng module ADS1115 với ESP32

Hướng dẫn nối module ADC 16 bit ADS1115 với ESP32 DevKit V1 qua I2C, cài thư viện Adafruit ADS1X15, đọc 4 kênh ra vôn, đo vi sai và chọn gain an toàn.

Tưới cây tự động với ESP32: cảm biến độ ẩm đất, relay và bơm mini, ngưỡng có trễ
Tưới cây tự động với ESP32: cảm biến độ ẩm đất, relay và bơm mini, ngưỡng có trễ

Làm hệ thống tưới cây tự động với ESP32: cảm biến độ ẩm đất điện dung, hiệu chỉnh khô ướt, relay điều khiển bơm mini, ngưỡng có trễ, giới hạn thời gian bơm và thí nghiệm STEM.

Hướng dẫn dùng mạch tạo trễ NE555 có relay: đấu dây và chỉnh thời gian trễ
Hướng dẫn dùng mạch tạo trễ NE555 có relay: đấu dây và chỉnh thời gian trễ

Hướng dẫn dùng mạch tạo trễ NE555 có relay bản 5 V và 12 V: nguyên lý tạo trễ, ý nghĩa các cọc VCC, GND, COM, CK, CB, cách chỉnh thời gian trễ, sơ đồ đấu dây và xử lý lỗi thường gặp.

Hướng dẫn giao tiếp I2C trên ESP32: quét địa chỉ, đổi chân SDA/SCL và nối nhiều thiết bị
Hướng dẫn giao tiếp I2C trên ESP32: quét địa chỉ, đổi chân SDA/SCL và nối nhiều thiết bị

Hướng dẫn dùng bus I2C trên ESP32 với Arduino IDE: quét địa chỉ thiết bị, đổi chân SDA và SCL, nối màn hình OLED và ADS1115 chung một bus, xử lý khi hai thiết bị trùng địa chỉ.

Tự làm máy vẽ (pen plotter) 2 trục bằng ESP32 và động cơ bước, tự viết bộ đọc G-code
Tự làm máy vẽ (pen plotter) 2 trục bằng ESP32 và động cơ bước, tự viết bộ đọc G-code

Tự làm máy vẽ 2 trục với ESP32, hai A4988, NEMA 17 và servo nhấc bút; chương trình tự đọc G-code G0 G1 G2 G3, nội suy Bresenham có tăng tốc, script Python gửi file G-code.

ESP32 làm mạch điều khiển bay cho flycam mini động cơ có chổi than: lọc bù, PID, bộ trộn, giá thử
ESP32 làm mạch điều khiển bay cho flycam mini động cơ có chổi than: lọc bù, PID, bộ trộn, giá thử

Dùng ESP32 làm mạch điều khiển bay cho flycam mini động cơ có chổi than: kênh MOSFET, bộ lọc bù, hai vòng PID, bộ trộn bốn động cơ, điều kiện an toàn và cách chỉnh PID trên giá thử.

Mạch hạ áp LM2596 và MP1584: cách chọn, chỉnh điện áp và cấp nguồn ESP32
Mạch hạ áp LM2596 và MP1584: cách chọn, chỉnh điện áp và cấp nguồn ESP32

So sánh mạch hạ áp LM2596 và MP1584, cách chỉnh điện áp ra bằng đồng hồ vạn năng trước khi nối tải, cấp nguồn 5 V cho ESP32 DevKit V1 từ nguồn 12 V và xử lý lỗi thường gặp.


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