Bài đầu tiên với ESP32 cho học sinh: đèn nhấp nháy và phát tên bằng mã Morse
Dự án STEM đầu tiên với ESP32 không cần linh kiện: nháy đèn LED có sẵn trên mạch, rồi phát tên của bé bằng mã Morse; cài Arduino IDE, giải thích code và trò chơi đoán tên.
Chương trình đầu tiên khi học lập trình mạch điện tử thường là làm một đèn nhấp nháy. Với mạch ESP32 DevKit V1, bài này không cần thêm linh kiện nào, vì trên mạch đã có sẵn một đèn LED. Chỉ cần mạch, một dây USB và máy tính, học sinh sẽ thấy dòng lệnh mình viết làm đèn sáng, tắt thật.
Bài viết có hai chương trình: đèn nhấp nháy đều, và phát tên của bé bằng mã Morse, một trò chơi giúp trẻ hiểu máy móc "nói chuyện" bằng tín hiệu bật, tắt như thế nào. Phù hợp với học sinh từ khoảng 10 tuổi, có người lớn đi cùng.
1. Chuẩn bị

- Một mạch ESP32 DevKit V1.
- Một dây USB nạp code đúng loại cổng của mạch. Dây chỉ dùng để sạc sẽ không nạp được chương trình.
- Máy tính cài Arduino IDE 2.x.
Đèn LED trên mạch ESP32 DevKit V1 nối với chân GPIO2; trên mạch thường là một đèn xanh nhỏ gần cổng USB. Ngoài đèn này, mạch còn một đèn nguồn luôn sáng khi cắm USB.
2. Cài đặt Arduino IDE cho ESP32
- Cài driver cho chip giao tiếp USB của mạch (CP2102 hoặc CH340) nếu máy chưa nhận cổng; xem trang Driver trên pyworld.vn.
- Mở Tools > Board > Boards Manager, tìm esp32 và cài gói esp32 by Espressif Systems.
- Chọn Tools > Board > esp32 > DOIT ESP32 DEVKIT V1.
- Cắm mạch vào máy tính, chọn đúng cổng ở Tools > Port.
Bài dùng gói esp32 phiên bản 3.3.10. Hai chương trình chỉ dùng lệnh có sẵn, không cần cài thư viện.
3. Chương trình 1: đèn nhấp nháy
// PYWORLD.VN - ESP32 DevKit V1: chương trình đầu tiên cho học sinh
// Nháy đèn LED có sẵn trên mạch (GPIO2): sáng 0,5 giây, tắt 0,5 giây
// Mở Serial Monitor ở tốc độ 115200 baud để xem đèn đang sáng hay tắt
const int LED_PIN = 2; // đèn LED xanh trên mạch ESP32 DevKit V1
const int THOI_GIAN_SANG = 500; // mili giây (1000 mili giây = 1 giây)
const int THOI_GIAN_TAT = 500;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT); // chân 2 là chân ra: ESP32 bật, tắt điện ở chân này
Serial.println("Bat dau nhay den");
}
void loop() {
digitalWrite(LED_PIN, HIGH); // bật đèn
Serial.println("Den SANG");
delay(THOI_GIAN_SANG); // chờ, đèn vẫn sáng
digitalWrite(LED_PIN, LOW); // tắt đèn
Serial.println("Den TAT");
delay(THOI_GIAN_TAT); // chờ, đèn vẫn tắt
} // hết loop() thì ESP32 tự chạy lại từ đầu loop()
Bấm nút Upload (mũi tên sang phải). Nạp xong, đèn xanh trên mạch sáng 0,5 giây, tắt 0,5 giây, lặp lại mãi. Mở Tools > Serial Monitor, chọn tốc độ 115200 baud để xem các dòng chương trình in ra.

Giải thích cho trẻ
setup()chạy một lần khi mạch vừa bật: chuẩn bị chân 2 làm chân ra, mở cổng in chữ.loop()chạy lặp lại mãi. Đây là vòng lặp, giống thẻ "lặp lại" trong trò chơi robot.digitalWrite(LED_PIN, HIGH)bật đèn;LOWtắt đèn.delay(500)bảo mạch đứng chờ 500 mili giây, tức nửa giây.
Thử thay đổi: đổi THOI_GIAN_SANG thành 100 và THOI_GIAN_TAT thành 900, nạp lại. Đèn chớp ngắn như đèn báo. Đổi cả hai thành 50: đèn nhấp nháy nhanh đến mức mắt gần như thấy sáng liên tục.
4. Mã Morse là gì?
Mã Morse dùng hai loại tín hiệu, chấm (ngắn) và gạch (dài), để ghi các chữ cái và chữ số. Trước khi có điện thoại, người ta dùng mã Morse để gửi điện tín, và ngày nay mã Morse vẫn được dùng trong vô tuyến nghiệp dư. Với trẻ, đây là ví dụ dễ hiểu về mã hóa: một chữ cái được đổi thành một dãy tín hiệu.

Quy ước thời gian của mã Morse quốc tế tính theo một đơn vị:
- chấm sáng 1 đơn vị, gạch sáng 3 đơn vị;
- giữa hai ký hiệu trong một chữ: tắt 1 đơn vị;
- giữa hai chữ: tắt 3 đơn vị; giữa hai từ: tắt 7 đơn vị.
5. Chương trình 2: phát tên của bé bằng mã Morse
// PYWORLD.VN - ESP32 DevKit V1: phát tên của bé bằng mã Morse
// Đèn LED trên mạch (GPIO2) nháy chấm, gạch theo mã Morse quốc tế
// Gõ tên (chữ không dấu) vào Serial Monitor 115200 baud rồi nhấn Enter
#include <ctype.h>
const int LED_PIN = 2;
const int DON_VI = 200; // độ dài 1 đơn vị thời gian (mili giây); chấm dài 1 đơn vị, gạch dài 3
// Mã Morse quốc tế cho chữ A đến Z và số 0 đến 9
const char* MA_CHU[26] = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."};
const char* MA_SO[10] = {"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----."};
String ten = "AN"; // tên phát lúc mới bật; gõ tên khác vào Serial Monitor để đổi
// Tra mã của một ký tự; ký tự không có trong bảng (chữ có dấu, dấu câu) trả về nullptr
const char* maCua(char c) {
c = toupper(c);
if (c >= 'A' && c <= 'Z') return MA_CHU[c - 'A'];
if (c >= '0' && c <= '9') return MA_SO[c - '0'];
return nullptr;
}
// Phát một ký hiệu: chấm sáng 1 đơn vị, gạch sáng 3 đơn vị, sau đó tắt 1 đơn vị
void phatKyHieu(char k) {
digitalWrite(LED_PIN, HIGH);
delay(k == '.' ? DON_VI : 3 * DON_VI);
digitalWrite(LED_PIN, LOW);
delay(DON_VI);
}
void phatChu(const String& s) {
for (unsigned int i = 0; i < s.length(); i++) {
char c = s[i];
if (c == ' ') { // giữa hai từ: tắt tổng cộng 7 đơn vị (3 đã có + 4)
Serial.print("/ ");
delay(4 * DON_VI);
continue;
}
const char* ma = maCua(c);
if (ma == nullptr) continue; // bỏ qua ký tự không có mã
Serial.print((char)toupper(c));
Serial.print(' ');
Serial.print(ma);
Serial.print(" ");
for (const char* p = ma; *p; p++) phatKyHieu(*p);
delay(2 * DON_VI); // giữa hai chữ: tắt tổng cộng 3 đơn vị (1 đã có + 2)
}
Serial.println();
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
Serial.println("Go ten (khong dau) roi nhan Enter");
}
void loop() {
if (Serial.available()) {
String moi = Serial.readStringUntil('\n');
moi.trim();
if (moi.length() > 0) ten = moi;
}
phatChu(ten);
delay(2000); // nghỉ 2 giây rồi phát lại
}
Nạp chương trình, mở Serial Monitor ở 115200 baud và chọn kiểu xuống dòng New Line. Lúc đầu mạch phát tên "AN". Gõ tên của bé bằng chữ không dấu, ví dụ MINH hoặc BAO AN, rồi nhấn Enter; từ lần phát sau, đèn nháy theo tên mới.

Chương trình làm gì?
MA_CHUvàMA_SOlà hai bảng tra: chữ A ở vị trí 0, chữ B ở vị trí 1… HàmmaCua()đổi chữ thường thành chữ hoa rồi tra bảng.phatKyHieu()bật đèn 1 hoặc 3 đơn vị tùy chấm hay gạch, rồi tắt 1 đơn vị.phatChu()đi lần lượt từng chữ của tên, phát mã của chữ đó, rồi tắt thêm 2 đơn vị để khoảng cách giữa hai chữ đủ 3 đơn vị. Gặp dấu cách thì tắt thêm 4 đơn vị để giữa hai từ đủ 7 đơn vị.- Chữ có dấu như "Ă", "Đ" không có trong bảng nên chương trình bỏ qua. Vì vậy cần gõ tên không dấu.
6. Trò chơi đoán tên
- Một bạn gõ một tên ngắn (2–4 chữ) mà người kia không nhìn thấy.
- Người đoán nhìn đèn, ghi lại chấm, gạch trên giấy.
- Dùng bảng mã Morse để dịch ra tên.
- Nếu khó quá, tăng
DON_VItừ 200 lên 400 để đèn nháy chậm hơn.
Trò chơi này rèn sự tập trung, ghi chép có hệ thống và kỹ năng giải mã.
7. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Không thấy cổng ở Tools > Port | Chưa cài driver; dây USB chỉ để sạc | Cài driver CP2102 hoặc CH340; đổi dây USB khác |
| Nạp báo lỗi kết nối, dừng ở "Connecting…" | Mạch chưa vào chế độ nạp | Giữ nút BOOT trên mạch khi IDE hiện "Connecting…", thả ra khi bắt đầu nạp |
| Nạp xong nhưng đèn không nháy | Bản mạch dùng chân khác cho đèn | Xem chữ in cạnh đèn trên mạch; thử đổi LED_PIN |
| Serial Monitor hiện ký tự lạ | Sai tốc độ baud | Chọn 115200 baud |
| Gõ tên mới không đổi | Chưa chọn kiểu xuống dòng | Chọn New Line ở Serial Monitor; chương trình chỉ đọc tên mới sau khi phát xong tên cũ |
8. Gợi ý dùng trong giờ học STEM
- Toán: tính thời gian phát tên của từng bạn trong lớp theo số đơn vị; tên nào dài nhất?
- Khoa học: đèn chớp nhanh đến mức nào thì mắt không còn thấy chớp? Thử 200, 100, 50, 20 mili giây và ghi lại.
- Lịch sử, xã hội: tìm hiểu tín hiệu cấp cứu SOS (... --- ...) và vì sao chọn dãy này.
- Thời lượng gợi ý: 2 buổi, mỗi buổi 45 phút: buổi 1 cài đặt và đèn nhấp nháy, buổi 2 mã Morse và trò chơi đoán tên.
9. Xem thêm
Bài trước trong loạt so sánh các bo mạch cho học sinh; bài tiếp theo dùng chân cảm ứng có sẵn trên ESP32 để chạm tay bật đèn, cũng không cần linh kiện. Khi muốn chỉnh độ sáng đèn thay vì chỉ bật, tắt, xem bài PWM trên ESP32: điều chỉnh độ sáng LED.
Xem thêm