Đàn piano trái cây với ESP32: 6 phím cảm ứng, còi phát nốt nhạc cho học sinh

Dự án STEM âm nhạc: nối sáu quả vào chân cảm ứng ESP32, chạm quả nào còi phát nốt đó; tần số Đô Rê Mi Fa Son La, giai điệu viết bằng hai mảng nốt và phách.

Chạm vào quả táo, mạch phát nốt Đô; chạm quả cam, mạch phát nốt Rê… Đàn piano trái cây là dự án STEM được trẻ rất thích vì vừa có âm nhạc, vừa có "phép màu" của điện. Bài viết hướng dẫn làm đàn 6 phím bằng ESP32, sáu quả bất kỳ và một còi nhỏ, rồi lập trình cho mạch tự chơi một giai điệu lưu trong mảng.

Qua dự án, học sinh học được ba điều: chân cảm ứng của ESP32 nhận biết cái chạm tay qua một vật dẫn điện, âm thanh cao hay thấp là do tần số, và một bản nhạc có thể viết thành hai dãy số: nốt nào và dài bao lâu.

1. Chuẩn bị

  • Một mạch ESP32 DevKit V1 và dây USB nạp code.
  • Tám dây cắm test board loại một đầu cái, một đầu đực: sáu dây cho sáu quả, hai dây cho còi.
  • Một còi thụ động (còn gọi là loa áp điện, buzzer thụ động). Loại này phát được nhiều nốt khác nhau. Còi chủ động chỉ kêu một tiếng cố định nên không dùng được.
  • Sáu quả hoặc rau củ mọng nước: táo, cam, chuối, thanh long, ổi, chanh… Đặt trên đĩa hoặc tờ giấy khô.
  • Arduino IDE đã cài gói esp32 và chọn board DOIT ESP32 DEVKIT V1.

Cách phân biệt hai loại còi: cấp điện 3,3 V trực tiếp vào hai chân, còi chủ động kêu "bíp" liên tục, còi thụ động chỉ kêu "tách" một tiếng. Nếu dùng còi thụ động loại cuộn dây (điện từ) thay vì loại áp điện, nên dùng dạng module có sẵn transistor để không lấy dòng quá lớn từ chân ESP32.

2. Nối dây

Sơ đồ nối: sáu quả táo, cam, chuối, thanh long, ổi, chanh nối vào chân D32, D33, D27, D14, D13, D4 của ESP32 DevKit V1 ứng với nốt Đô, Rê, Mi, Fa, Son, La; còi thụ động nối chân D25 và GND; bảng liệt kê chân và vật nối
Hình minh họa: mỗi quả một dây vào một chân cảm ứng.
Chân nối của đàn piano trái cây
Chân ESP32Nối vớiNốt
D32quả táoĐô
D33quả camRê
D27quả chuốiMi
D14quả thanh longFa
D13quả ổiSon
D4quả chanhLa
D25chân + của còi
GNDchân − của còi
  • Cắm đầu cái của dây vào chân mạch, đầu đực cắm sâu khoảng 1–2 cm vào quả.
  • Các quả không được chạm nhau, và không đặt trên đĩa ướt hay mặt bàn kim loại: nước hoặc kim loại nối các quả lại thì chạm một quả, mạch nhận ra nhiều phím.
  • Sáu chân D32, D33, D27, D14, D13, D4 đều là chân cảm ứng. Bài không dùng D2, D12, D15 (cũng là chân cảm ứng) vì D2 nối với đèn trên mạch, còn D12, D15 ảnh hưởng tới cách mạch khởi động.

3. Vì sao chạm vào quả thì mạch biết?

Quả chứa nhiều nước và muối khoáng nên dẫn điện. Khi nối vào chân cảm ứng, quả giống một tấm kim loại lớn gắn ở đầu dây. Khi ngón tay chạm vào quả, khả năng tích điện (điện dung) của chân đó tăng lên, và con số ESP32 đọc được bằng lệnh touchRead() thay đổi rõ rệt.

Mỗi quả có kích thước và độ ẩm khác nhau nên con số lúc chưa chạm cũng khác nhau. Vì vậy, lúc khởi động, chương trình đo giá trị nền của từng phím, rồi so độ lệch: lệch quá 30 % là đang chạm, trở về dưới 15 % là đã nhả tay. Hai ngưỡng khác nhau giúp nốt nhạc không bị ngắt quãng khi tay hơi run.

4. Mỗi phím một tần số

Biểu đồ cột tần số sáu nốt: Đô 262 Hz ở D32, Rê 294 Hz ở D33, Mi 330 Hz ở D27, Fa 349 Hz ở D14, Son 392 Hz ở D13, La 440 Hz ở D4; ghi chú âm thanh là dao động và nốt Đô cao hơn một quãng tám có tần số gấp đôi, khoảng 523 Hz
Hình minh họa: nốt càng cao, tần số càng lớn.

Âm thanh là dao động. Màng còi rung bao nhiêu lần trong một giây thì gọi là tần số, đơn vị héc (Hz). Rung càng nhanh, âm nghe càng cao. Nốt La chuẩn dùng để lên dây đàn là 440 Hz. Lệnh tone(chân, tần số) cho còi rung đúng tần số đó, lệnh noTone(chân) tắt tiếng.

5. Chương trình 1: đàn piano 6 phím

// PYWORLD.VN - ESP32 DevKit V1: đàn piano trái cây 6 phím cảm ứng
// Sáu chân cảm ứng nối với sáu quả; chạm quả nào, còi phát nốt đó (Đô Rê Mi Fa Son La)
// Còi thụ động (loa nhỏ áp điện) nối D25 và GND; lúc khởi động không chạm vào quả

const int CHAN_COI = 25;
const int SO_PHIM = 6;
const int CHAN_PHIM[SO_PHIM] = {32, 33, 27, 14, 13, 4};       // D32, D33, D27, D14, D13, D4
const int TAN_SO[SO_PHIM] = {262, 294, 330, 349, 392, 440};   // Hz: Đô, Rê, Mi, Fa, Son, La (quãng 4)
const char* TEN_NOT[SO_PHIM] = {"Do", "Re", "Mi", "Fa", "Son", "La"};
const float LECH_CHAM = 0.30;   // lệch hơn 30 % so với giá trị nền: đang chạm
const float LECH_NHA = 0.15;    // lệch dưới 15 %: đã nhả

uint32_t giaTriNen[SO_PHIM];
bool dangCham[SO_PHIM];
int notDangPhat = -1;           // -1: đang im lặng

uint32_t docTrungBinh(int chan, int soLan) {
  uint32_t tong = 0;
  for (int i = 0; i < soLan; i++) {
    tong += touchRead(chan);
    delay(2);
  }
  return tong / soLan;
}

float doLech(int phim, uint32_t giaTri) {
  float chenh = (float)giaTri - (float)giaTriNen[phim];
  if (chenh < 0) chenh = -chenh;
  return chenh / giaTriNen[phim];
}

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("Dung cham vao qua, dang do gia tri nen...");
  for (int p = 0; p < SO_PHIM; p++) {
    giaTriNen[p] = docTrungBinh(CHAN_PHIM[p], 30);
    if (giaTriNen[p] == 0) giaTriNen[p] = 1;
    dangCham[p] = false;
    Serial.printf("Phim %s (chan %d): nen %u\n", TEN_NOT[p], CHAN_PHIM[p], (unsigned)giaTriNen[p]);
  }
  Serial.println("San sang! Cham vao qua de choi dan");
}

void loop() {
  int notMoi = -1;
  for (int p = 0; p < SO_PHIM; p++) {
    float lech = doLech(p, docTrungBinh(CHAN_PHIM[p], 3));
    if (!dangCham[p] && lech > LECH_CHAM) dangCham[p] = true;
    else if (dangCham[p] && lech < LECH_NHA) dangCham[p] = false;
    if (dangCham[p] && notMoi == -1) notMoi = p;   // chạm nhiều quả: phát nốt thấp nhất
  }

  if (notMoi != notDangPhat) {                     // chỉ đổi âm thanh khi có thay đổi
    if (notMoi == -1) {
      noTone(CHAN_COI);
    } else {
      tone(CHAN_COI, TAN_SO[notMoi]);
      Serial.println(TEN_NOT[notMoi]);
    }
    notDangPhat = notMoi;
  }
  delay(10);
}

Nạp chương trình khi tay không chạm vào quả nào, vì 30 lần đọc đầu tiên dùng để đo giá trị nền. Mở Serial Monitor ở 115200 baud để xem giá trị nền của từng phím, sau đó chạm từng quả: còi phát nốt, Serial Monitor in tên nốt.

  • Ba mảng song song: CHAN_PHIM, TAN_SO, TEN_NOT có cùng thứ tự, nên phần tử thứ nhất của cả ba cùng nói về phím Đô. Muốn đổi quả nào phát nốt nào, chỉ cần đổi thứ tự trong mảng.
  • Chạm nhiều quả cùng lúc: còi chỉ phát được một âm mỗi lúc, nên chương trình chọn nốt thấp nhất trong các quả đang chạm.
  • Chỉ đổi âm khi có thay đổi: biến notDangPhat nhớ nốt đang kêu. Nếu không có biến này, chương trình gọi tone() liên tục mỗi 10 ms và âm thanh có thể bị rè.

6. Chương trình 2: mạch tự chơi một giai điệu

Giai điệu 32 nốt vẽ thành hai hàng cột: cột cao là nốt cao, cột rộng là nốt dài; hàng đầu Đô Rê Mi Đô, Đô Rê Mi Đô, Mi Fa Son, Mi Fa Son; hàng sau Son La Son Fa Mi Đô hai lần, rồi Đô Son thấp Đô hai lần; 32 nốt, 32 phách, mỗi phách 300 mili giây nên cả giai điệu dài 9,6 giây
Hình minh họa: bản nhạc viết thành hai dãy số.

Chương trình thứ hai chơi giai điệu dân gian Frère Jacques, ở Việt Nam quen thuộc với tên bài hát thiếu nhi "Kìa con bướm vàng". Bản nhạc được viết thành hai mảng: NOT_NHAC ghi tần số từng nốt, PHACH ghi mỗi nốt dài mấy phách. Còi giữ nguyên nối D25 và GND; quả chanh ở D4 dùng làm nút "phát lại".

// PYWORLD.VN - ESP32 DevKit V1: phát giai điệu lưu trong mảng
// Giai điệu dân gian Frère Jacques (ở Việt Nam quen với tên "Kìa con bướm vàng")
// Còi thụ động nối D25 và GND; chạm dây ở D4 (hoặc quả nối D4) để phát lại

const int CHAN_COI = 25;
const int CHAN_CHAM = 4;
const int NHIP = 300;            // độ dài một phách (mili giây); số nhỏ hơn thì nhạc nhanh hơn

// Tần số các nốt (Hz); 0 là lặng
const int SON3 = 196, DO = 262, RE = 294, MI = 330, FA = 349, SON = 392, LA = 440;

// Giai điệu: mỗi nốt đi kèm số phách
const int NOT_NHAC[] = {DO, RE, MI, DO,  DO, RE, MI, DO,  MI, FA, SON,  MI, FA, SON,
                        SON, LA, SON, FA, MI, DO,  SON, LA, SON, FA, MI, DO,  DO, SON3, DO,  DO, SON3, DO};
const float PHACH[] =   {1, 1, 1, 1,     1, 1, 1, 1,     1, 1, 2,      1, 1, 2,
                        0.5, 0.5, 0.5, 0.5, 1, 1,   0.5, 0.5, 0.5, 0.5, 1, 1,   1, 1, 2,   1, 1, 2};
const int SO_NOT = sizeof(NOT_NHAC) / sizeof(NOT_NHAC[0]);

uint32_t giaTriNen = 1;

void phatGiaiDieu() {
  Serial.println("Phat giai dieu");
  for (int i = 0; i < SO_NOT; i++) {
    int thoiGian = (int)(PHACH[i] * NHIP);
    if (NOT_NHAC[i] > 0) tone(CHAN_COI, NOT_NHAC[i]);
    delay(thoiGian * 9 / 10);    // phát 90 % độ dài nốt
    noTone(CHAN_COI);
    delay(thoiGian / 10);        // ngắt 10 % để hai nốt giống nhau vẫn nghe tách ra
  }
}

void setup() {
  Serial.begin(115200);
  delay(500);
  uint32_t tong = 0;
  for (int i = 0; i < 30; i++) {
    tong += touchRead(CHAN_CHAM);
    delay(2);
  }
  giaTriNen = tong / 30;
  if (giaTriNen == 0) giaTriNen = 1;
  Serial.printf("Giai dieu co %d not\n", SO_NOT);
  phatGiaiDieu();
}

void loop() {
  uint32_t giaTri = touchRead(CHAN_CHAM);
  float lech = ((float)giaTri - (float)giaTriNen) / giaTriNen;
  if (lech < 0) lech = -lech;
  if (lech > 0.30) {             // chạm vào dây D4: phát lại
    phatGiaiDieu();
    delay(500);
  }
  delay(20);
}
  • Nhịp: mỗi phách 300 ms. Đổi NHIP thành 200 thì nhạc nhanh hơn, thành 450 thì chậm hơn.
  • Nốt kêu 90 %, nghỉ 10 %: nếu không có khoảng nghỉ ngắn, hai nốt Đô liền nhau sẽ nghe thành một nốt dài.
  • Đặt tên cho tần số: dòng const int SON3 = 196, DO = 262, … giúp mảng giai điệu dễ đọc như bản nhạc. SON3 là nốt Son thấp hơn một quãng tám (196 Hz, bằng một nửa 392 Hz).

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

Lỗi thường gặp và cách xử lý
Hiện tượngNguyên nhân thường gặpCách xử lý
Không có tiếngDùng còi chủ động; dây còi cắm sai chânĐổi sang còi thụ động; kiểm tra dây ở D25 và GND
Còi kêu ngay khi không chạmLúc khởi động đang chạm vào quả; hai quả chạm nhauTách các quả, bỏ tay ra rồi bấm nút EN trên mạch để đo lại giá trị nền
Chạm quả không kêuDây chưa cắm sâu; quả khô, nhỏCắm dây sâu hơn, đổi quả mọng nước; giảm LECH_CHAM xuống 0.20
Chạm một quả, kêu sai nốtQuả đặt trên đĩa ướt hoặc mặt kim loại; thứ tự dây khác bảng nốiĐặt quả trên giấy khô; so lại bảng nối với mảng CHAN_PHIM
Âm kêu chập chờn khi giữ tayTay chạm nhẹ, độ lệch dao động quanh ngưỡngChạm chắc tay hơn, hoặc giảm LECH_NHA xuống 0.10

8. Thử thách mở rộng

  • Viết bản nhạc riêng: giai điệu Twinkle Twinkle Little Star bắt đầu bằng Đô Đô Son Son La La Son (nốt cuối dài 2 phách), Fa Fa Mi Mi Rê Rê Đô (nốt cuối dài 2 phách). Hãy viết hai mảng cho đoạn này.
  • Đàn quãng cao: nhân đôi các số trong mảng TAN_SO (524, 588…) rồi nghe lại. Nhận xét: nốt vẫn "giống" nhưng cao hơn.
  • Thí nghiệm vật dẫn: thay một quả bằng cốc nước, miếng giấy bạc, cọng rau, cục tẩy. Vật nào làm phím hoạt động, vật nào không? Vì sao?
  • Đàn người: cầm đầu dây thay cho quả. Chạm vào tay bạn đang cầm dây thì còi có kêu không?

9. Gợi ý cho phụ huynh và giáo viên

  • Liên môn: dự án nối môn Âm nhạc (tên nốt, cao độ, phách), Khoa học tự nhiên lớp 7 (độ cao của âm và tần số) và Tin học (mảng, vòng lặp).
  • Bắt đầu với một quả: học sinh nhỏ nên làm một phím trước, rồi tăng dần lên sáu phím. Người lớn nối dây còi, trẻ cắm dây vào quả.
  • Phân vai trong nhóm: một bạn nối dây, một bạn sửa mảng giai điệu, một bạn biểu diễn. Cuối buổi, mỗi nhóm chơi một bài cho cả lớp đoán tên.
  • An toàn: mạch chạy bằng nguồn USB 5 V nên an toàn khi chạm. Không để nước trái cây chảy vào mạch; rửa tay sau khi chơi và không ăn quả đã cắm dây.
  • Thời lượng: 2 buổi 45 phút. Buổi 1: nối dây, đàn 6 phím. Buổi 2: giai điệu trong mảng và viết bản nhạc riêng.

10. Xem thêm

Hàm tone() tạo âm thanh bằng tín hiệu xung, cùng loại tín hiệu trong bài PWM trên ESP32. Trong loạt phát triển tư duy cho trẻ bằng STEM và lập trình còn có các bài: chạm tay bật đèn với chân cảm ứng ESP32 (giải thích kỹ giá trị nền và hai ngưỡng), nháy đèn và mã Morse với ESP32, trò chơi đo phản xạ với ESP32 và màn hình OLED.


Tin tức liên quan

Liên kết chuẩn đo lường (traceability): chuỗi hiệu chuẩn từ đơn vị SI tới thiết bị hiện trường
Liên kết chuẩn đo lường (traceability): chuỗi hiệu chuẩn từ đơn vị SI tới thiết bị hiện trường

Liên kết chuẩn đo lường là gì: chuỗi hiệu chuẩn không đứt đoạn từ đơn vị SI, chuẩn quốc gia tới thiết bị hiện trường, CIPM MRA, ILAC MRA, VILAS, các trường hợp chuỗi bị đứt và cách kiểm tra.

RS-485 là gì: cặp dây vi sai, điện trở đầu cuối và cách dùng module MAX485 với ESP32
RS-485 là gì: cặp dây vi sai, điện trở đầu cuối và cách dùng module MAX485 với ESP32

Giải thích chuẩn RS-485: tín hiệu vi sai trên hai dây A, B, điện trở đầu cuối 120 Ω, truyền bán song công với chân DE, RE. Kèm sơ đồ nối module MAX485 và chương trình hỏi đáp giữa hai mạch ESP32.

Cảm biến gia tốc áp điện RION PV-90B (charge output)
Cảm biến gia tốc áp điện RION PV-90B (charge output)

Trong lĩnh vực đo rung động và phân tích dao động, yêu cầu về độ chính xác, tốc độ phản hồi và độ bền cơ học ngày càng trở nên khắt khe hơn. Đặc biệt với các ứng dụng trong lĩnh vực nghiên cứu kết cấu, cơ học dao động, kiểm định máy móc và đo modal, một cảm biến nhỏ gọn nhưng có độ nhạy cao là yếu tố sống còn. Cảm biến RION PV-90B chính là một trong những đại diện xuất sắc của dòng cảm biến gia tốc piezoelectric hiện nay – đến từ thương hiệu danh tiếng RION – Nhật Bản.

Độ không đảm bảo đo theo GUM: sáu bước tính, loại A, loại B và ví dụ ngân sách hiệu chuẩn áp kế
Độ không đảm bảo đo theo GUM: sáu bước tính, loại A, loại B và ví dụ ngân sách hiệu chuẩn áp kế

Cách tính độ không đảm bảo đo theo GUM: sáu bước, đánh giá loại A và loại B, phân bố chuẩn, chữ nhật, tam giác, ví dụ ngân sách hiệu chuẩn áp kế, cách làm tròn và lỗi hay gặp.

Cấp nguồn ESP32 bằng pin 18650 sạc qua jack Type-C tích hợp mạch sạc
Cấp nguồn ESP32 bằng pin 18650 sạc qua jack Type-C tích hợp mạch sạc

Sơ đồ nguồn cho ESP32 chạy pin 18650, sạc qua jack Type-C trên vỏ hộp: vì sao cần mạch đổi áp, chọn tăng áp 5 V hay ổn áp 3,3 V, ước tính thời gian chạy pin, đo mức pin và các lỗi nguồn thường gặp.

Quy trình thiết kế kỹ thuật trong dự án STEM: 5 hoạt động, ví dụ làm cối xay gió nâng vật
Quy trình thiết kế kỹ thuật trong dự án STEM: 5 hoạt động, ví dụ làm cối xay gió nâng vật

Dự án STEM là gì và quy trình thiết kế kỹ thuật 8 bước, 5 hoạt động theo hướng dẫn của Bộ GD&ĐT, minh họa bằng dự án cối xay gió nâng vật với phiếu thử nghiệm công bằng.

Cảm biến nhiệt độ: cấu tạo, nguyên lý và cách chế tạo cặp nhiệt điện, RTD, NTC, IC bán dẫn
Cảm biến nhiệt độ: cấu tạo, nguyên lý và cách chế tạo cặp nhiệt điện, RTD, NTC, IC bán dẫn

Cấu tạo, nguyên lý và cách chế tạo năm họ cảm biến nhiệt độ: cặp nhiệt điện, Pt100, NTC, IC bán dẫn, hồng ngoại; cách chọn theo ứng dụng và ví dụ đọc NTC 10 kΩ bằng ESP32.

Máy đo rung: kiểm tra hiện trường bằng bộ hiệu chuẩn 159,2 Hz, vị trí đo và vùng đánh giá ISO 20816
Máy đo rung: kiểm tra hiện trường bằng bộ hiệu chuẩn 159,2 Hz, vị trí đo và vùng đánh giá ISO 20816

Kiểm tra máy đo rung bằng bộ hiệu chuẩn cầm tay 159,2 Hz, 10 m/s², đo đúng vị trí và hướng trên ổ đỡ, đánh giá vùng A, B, C, D theo ISO 20816 và hiệu chuẩn máy đo rung ở phòng thí nghiệm.


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