Hướng dẫn dùng PWM trên ESP32: điều chỉnh độ sáng LED bằng analogWrite và LEDC
- 1. PWM là gì?
- 2. PWM trên ESP32: bộ LEDC
- 3. Linh kiện cần chuẩn bị
- 4. Sơ đồ nối dây
- 5. Cách 1: dùng analogWrite()
- 6. Cách 2: dùng LEDC và chỉnh độ sáng bằng biến trở
- 7. Giải thích các lệnh chính
- 8. Biến thể: để phần cứng tự làm mờ dần
- 9. Chọn tần số, độ phân giải và điều khiển tải lớn hơn
- 10. Xử lý lỗi thường gặp
Hướng dẫn tạo PWM trên ESP32 bằng Arduino IDE: analogWrite, ledcAttach và ledcWrite của gói esp32 3.x, chỉnh độ sáng LED bằng biến trở, chọn tần số và độ phân giải.
Chân GPIO của ESP32 chỉ xuất được hai mức 0 V và 3,3 V, vậy làm sao để LED sáng mờ hay động cơ quay chậm? Câu trả lời là PWM: bật tắt chân thật nhanh và thay đổi tỉ lệ thời gian bật. Bài viết giải thích PWM, hai cách tạo PWM trên ESP32 bằng Arduino IDE (analogWrite() và bộ LEDC), cách chỉnh độ sáng LED bằng biến trở và cách chọn tần số, độ phân giải.
1. PWM là gì?
PWM (Pulse Width Modulation, điều chế độ rộng xung) là tín hiệu xung vuông có tần số cố định, trong đó phần thời gian ở mức cao của mỗi chu kỳ thay đổi được. Tỉ lệ này gọi là duty. Duty càng lớn thì điện áp trung bình trên chân càng cao và LED càng sáng.
Khi tần số đủ cao (từ khoảng 100 Hz trở lên), mắt không còn thấy LED nhấp nháy mà chỉ thấy độ sáng thay đổi.
2. PWM trên ESP32: bộ LEDC
ESP32 tạo PWM bằng khối phần cứng tên là LEDC (LED Control). Khác với Arduino Uno chỉ có vài chân PWM cố định, ESP32 cho phép:
- Xuất PWM trên hầu hết các chân GPIO có chức năng ngõ ra (không dùng được GPIO34–39 vì chỉ là ngõ vào).
- Dùng tới 16 kênh PWM cùng lúc.
- Tự chọn tần số và độ phân giải. Độ phân giải là số bit dùng để chia duty: 8 bit cho 256 mức (0–255), 12 bit cho 4096 mức (0–4095).
Tần số và độ phân giải ràng buộc lẫn nhau: độ phân giải càng cao thì tần số tối đa càng thấp.
| Độ phân giải | Khoảng giá trị duty | Tần số tối đa |
|---|---|---|
| 8 bit | 0–255 | Khoảng 312 kHz |
| 10 bit | 0–1023 | Khoảng 78 kHz |
| 12 bit | 0–4095 | Khoảng 19,5 kHz |
| 13 bit | 0–8191 | Khoảng 9,7 kHz |
| 16 bit | 0–65535 | Khoảng 1,2 kHz |
Các giá trị trên tính theo công thức tần số tối đa = 80 MHz / 2số bit.
3. Linh kiện cần chuẩn bị
- ESP32 DevKit V1 30 chân (hoặc bản dùng chip CH340) và cáp USB có truyền dữ liệu.
- Một LED 5 mm và một điện trở 220 Ω.
- Một biến trở 10 kΩ (dùng từ mục 6).
- Breadboard MB-102 và dây cắm test board.
- Máy tính đã cài Arduino IDE 2.x và gói esp32 by Espressif Systems phiên bản 3.x (bài dùng 3.3.10). Không cần cài thêm thư viện.
4. Sơ đồ nối dây
| Linh kiện | ESP32 DevKit V1 | Ghi chú |
|---|---|---|
| Điện trở 220 Ω nối tiếp chân dài (+) của LED | D18 / GPIO18 | Chân xuất PWM. |
| Chân ngắn (−) của LED | GND | Mass chung. |
| Chân giữa của biến trở | D34 / GPIO34 | Ngõ vào analog, thuộc nhóm ADC1. |
| Hai chân ngoài của biến trở | 3V3 và GND | Đổi chỗ hai chân này chỉ làm đảo chiều vặn. |
Điện trở 220 Ω giới hạn dòng qua LED ở khoảng 6 mA, an toàn cho chân GPIO. Không nối LED thẳng vào chân GPIO mà không có điện trở. Chỉ cấp cho biến trở 3,3 V: ngõ vào analog của ESP32 không chịu được 5 V.
5. Cách 1: dùng analogWrite()
Đây là cách ngắn nhất và quen thuộc với người đã dùng Arduino. Chương trình cho LED sáng dần rồi tối dần:
/*
PYWORLD.VN - ESP32 PWM bằng analogWrite(): LED sáng dần rồi tối dần
LED: GPIO18 -> điện trở 220 ohm -> chân dài của LED; chân ngắn của LED -> GND
Không cần cài thêm thư viện (gói esp32 phiên bản 3.x)
*/
#define LED_PIN 18
void setup() {
// analogWrite() dùng cấu hình mặc định: độ phân giải 8 bit (0-255), tần số 1000 Hz
}
void loop() {
// Sáng dần
for (int doSang = 0; doSang <= 255; doSang++) {
analogWrite(LED_PIN, doSang);
delay(8);
}
// Tối dần
for (int doSang = 255; doSang >= 0; doSang--) {
analogWrite(LED_PIN, doSang);
delay(8);
}
}
Chọn board DOIT ESP32 DEVKIT V1 rồi nạp chương trình. analogWrite(chân, giá trị) dùng độ phân giải 8 bit và tần số 1000 Hz; giá trị 0 là tắt hẳn, 255 là sáng nhất. Có thể đổi hai thông số này bằng analogWriteResolution(chân, số bit) và analogWriteFrequency(chân, tần số).
6. Cách 2: dùng LEDC và chỉnh độ sáng bằng biến trở
Các hàm LEDC cho phép chọn rõ tần số và độ phân giải ngay từ đầu. Chương trình dưới đây dùng PWM 12 bit, trùng với độ phân giải 12 bit của analogRead(), nên giá trị đọc từ biến trở được dùng thẳng làm duty:
/*
PYWORLD.VN - ESP32 PWM bằng LEDC: chỉnh độ sáng LED bằng biến trở
LED: GPIO18 -> điện trở 220 ohm -> chân dài của LED; chân ngắn của LED -> GND
Biến trở 10 kΩ: hai chân ngoài nối 3V3 và GND, chân giữa nối GPIO34
Không cần cài thêm thư viện (gói esp32 phiên bản 3.x)
*/
#define LED_PIN 18
#define BIEN_TRO_PIN 34 // Chân ADC1, chỉ dùng làm ngõ vào
#define TAN_SO_HZ 5000 // Tần số PWM
#define DO_PHAN_GIAI 12 // 12 bit: duty từ 0 đến 4095
void setup() {
Serial.begin(115200);
delay(500);
// Gắn chân LED vào bộ PWM với tần số và độ phân giải đã chọn
if (!ledcAttach(LED_PIN, TAN_SO_HZ, DO_PHAN_GIAI)) {
Serial.println("Khong cau hinh duoc PWM! Kiem tra tan so va do phan giai.");
while (true) delay(1000);
}
}
void loop() {
int giaTri = analogRead(BIEN_TRO_PIN); // 0-4095, cùng 12 bit với PWM
ledcWrite(LED_PIN, giaTri); // Dùng thẳng giá trị đọc được làm duty
Serial.printf("Bien tro: %4d -> duty: %5.1f %%\n", giaTri, giaTri * 100.0 / 4095);
delay(100);
}
Mở Serial Monitor ở 115200 baud và vặn biến trở: LED sáng dần theo góc vặn.
7. Giải thích các lệnh chính
ledcAttach(LED_PIN, TAN_SO_HZ, DO_PHAN_GIAI): gắn một chân vào bộ LEDC với tần số 5000 Hz và độ phân giải 12 bit. ESP32 tự chọn kênh còn trống. Hàm trả vềfalsenếu tần số và độ phân giải không đi được với nhau.ledcWrite(LED_PIN, duty): đặt duty cho chân đó. Với 12 bit, duty chạy từ 0 đến 4095.analogRead(BIEN_TRO_PIN): đọc điện áp ở chân giữa biến trở, kết quả từ 0 đến 4095.giaTri * 100.0 / 4095: đổi duty sang phần trăm để dễ theo dõi.
Mắt người cảm nhận độ sáng không đều theo duty: từ 0 lên 25 % thấy sáng lên rất nhanh, còn từ 75 % lên 100 % gần như không khác. Độ phân giải 12 bit cho nhiều mức ở vùng duty thấp nên LED mờ dần mượt hơn 8 bit.
8. Biến thể: để phần cứng tự làm mờ dần
Bộ LEDC có sẵn chức năng thay đổi duty dần theo thời gian. Lệnh ledcFade() chỉ cần duty đầu, duty cuối và thời gian; phần cứng tự làm phần còn lại, chương trình không phải chạy vòng lặp:
/*
PYWORLD.VN - ESP32 PWM bằng LEDC: để phần cứng tự làm LED sáng dần, tối dần (ledcFade)
LED: GPIO18 -> điện trở 220 ohm -> chân dài của LED; chân ngắn của LED -> GND
Không cần cài thêm thư viện (gói esp32 phiên bản 3.x)
*/
#define LED_PIN 18
#define TAN_SO_HZ 5000
#define DO_PHAN_GIAI 12
#define DUTY_LON_NHAT 4095
#define THOI_GIAN_MS 2000
void setup() {
ledcAttach(LED_PIN, TAN_SO_HZ, DO_PHAN_GIAI);
}
void loop() {
// Sáng dần trong 2 giây; lệnh trả về ngay, phần cứng tự thay đổi duty
ledcFade(LED_PIN, 0, DUTY_LON_NHAT, THOI_GIAN_MS);
delay(THOI_GIAN_MS + 200);
// Tối dần trong 2 giây
ledcFade(LED_PIN, DUTY_LON_NHAT, 0, THOI_GIAN_MS);
delay(THOI_GIAN_MS + 200);
}
Vì ledcFade() trả về ngay, trong lúc LED đang đổi độ sáng chương trình vẫn có thể làm việc khác thay cho lệnh delay().
9. Chọn tần số, độ phân giải và điều khiển tải lớn hơn
- LED: tần số 1–5 kHz, độ phân giải 8 đến 12 bit.
- Động cơ DC qua mạch công suất: thường dùng 5–20 kHz để giảm tiếng rít.
- Servo RC: tần số 50 Hz, độ phân giải 16 bit; xung điều khiển 1–2 ms tương ứng duty khoảng 3277–6554.
- Nhiều chân cùng lúc: gọi
ledcAttach()cho từng chân. Các chân dùng cùng tần số và độ phân giải được ghép chung một bộ định thời. Mỗi nhóm 8 kênh có 4 bộ định thời, nên trong 8 chân đầu tiên dùng được tối đa 4 cấu hình tần số khác nhau. - Tải lớn hơn một LED: mỗi chân GPIO chỉ nên cấp dòng dưới khoảng 20 mA. Dải LED, động cơ hay quạt phải điều khiển qua MOSFET hoặc mạch công suất, chân GPIO chỉ đưa tín hiệu PWM.
- Không dùng PWM với relay cơ: module relay chỉ để đóng ngắt, tiếp điểm cơ khí không theo kịp xung PWM.
10. Xử lý lỗi thường gặp
- Lỗi biên dịch "ledcSetup was not declared in this scope" hoặc "ledcAttachPin was not declared": chương trình viết cho gói esp32 phiên bản 2.x. Từ phiên bản 3.0, hai lệnh
ledcSetup()vàledcAttachPin()được gộp thànhledcAttach(chân, tần số, độ phân giải), vàledcWrite()nhận số chân thay cho số kênh. - LED không sáng: kiểm tra chiều LED (chân dài nối về phía điện trở và GPIO18), điện trở và dây GND. Thử đổi
LED_PINthành 2 để dùng LED có sẵn trên mạch DevKit V1. - LED chỉ bật hoặc tắt, không mờ dần: giá trị duty vượt quá khoảng của độ phân giải, ví dụ ghi 4095 khi đang dùng 8 bit.
- Serial Monitor báo "Khong cau hinh duoc PWM!": tần số quá cao so với độ phân giải đã chọn. Xem bảng ở mục 2 rồi giảm tần số hoặc giảm số bit.
- Số đọc biến trở nhảy lung tung: chân giữa biến trở chưa nối chắc, hoặc đang dùng chân thuộc nhóm ADC2 trong khi bật Wi-Fi. Dùng các chân ADC1 là GPIO32–39.
- Vặn gần hết về một đầu thì độ sáng không đổi nữa: ADC của ESP32 có vùng chết ở hai đầu dải đo: dưới khoảng 0,15 V đọc ra 0, và gần 3,3 V đã đọc ra 4095. Đây là đặc tính của chip.
- Không nạp được chương trình: dùng cáp USB có truyền dữ liệu, chọn đúng cổng COM và giữ nút BOOT khi IDE dừng ở dòng Connecting...
Xem thêm: ESP32 điều khiển module relay 4 kênh, đo điện áp 16 bit bằng ADS1115 với ESP32 khi cần đọc analog chính xác hơn và bắt đầu MicroPython trên ESP32 với Thonny.
Xem thêm