Micro I2S INMP441 với ESP32: đo mức âm thanh dBFS, dB SPL và vỗ tay hai lần bật tắt đèn
Khung I2S của micro INMP441, nối với ESP32, đổi số đo 24 bit ra dBFS và ước tính dB SPL, nhận tiếng vỗ tay bằng mức nền và độ vọt, bật tắt đèn khi vỗ tay hai lần.
INMP441 là micro MEMS nhỏ cỡ hạt gạo, có sẵn bộ chuyển đổi tương tự – số 24 bit và gửi âm thanh ra theo chuẩn số I2S. Khác với micro điện dung có mạch khuếch đại (ra điện áp tương tự, đọc bằng ADC), INMP441 nối thẳng vào khối I2S của ESP32 nên ít nhiễu và không cần chỉnh biến trở. Bài này giải thích khung I2S, cách đổi số đo ra dBFS và ước tính dB SPL, rồi viết chương trình đo mức âm thanh và nhận tiếng vỗ tay hai lần để bật tắt đèn, chỉ dùng thư viện có sẵn trong gói esp32.
1. I2S và khung dữ liệu của INMP441

I2S là chuẩn truyền âm thanh số dùng ba dây:
- SCK (xung bit): ESP32 phát ra, mỗi xung một bit. INMP441 cần 64 xung cho mỗi mẫu (32 cho mỗi kênh), nên ở 16 000 mẫu/giây SCK chạy 1,024 MHz.
- WS (chọn kênh): mức thấp là kênh trái, mức cao là kênh phải. Tần số của WS chính là tần số lấy mẫu.
- SD (dữ liệu): micro gửi 24 bit, bit cao trước, bắt đầu một xung sau khi WS đổi mức.
Chân L/R của INMP441 chọn kênh micro gửi: nối GND thì gửi ở kênh trái, nối 3,3 V thì gửi ở kênh phải. Hai micro có L/R khác nhau dùng chung SCK, WS, SD được, thành micro stereo. Trong khe còn lại, micro thả nổi chân SD.
Chương trình đọc mỗi kênh thành một số 32 bit. 24 bit dữ liệu nằm ở phần cao, nên dịch phải có dấu 8 bit sẽ được số 24 bit, từ −8 388 608 tới 8 388 607. Chương trình đọc cả hai kênh (stereo) rồi tự chọn kênh có tín hiệu: kênh không có micro đọc ra số gần như không đổi, kênh có micro luôn có chút nhiễu nền. Cách này tránh được lỗi đảo kênh trái/phải và không phụ thuộc chân L/R được nối thế nào.
2. Linh kiện cần chuẩn bị
- ESP32 DevKit V1 30 chân (hoặc loại 38 chân) và dây USB nạp code.
- Module micro INMP441 (bo tròn hoặc vuông, 6 chân).
- Dây cắm ngắn và breadboard.
- Muốn bật tắt đèn thật thay cho đèn trên bo: module relay.
3. Nối INMP441 với ESP32

| INMP441 | ESP32 DevKit V1 | Ghi chú |
|---|---|---|
| VDD | 3V3 | Không cấp 5 V |
| GND | GND | |
| L/R | GND | Micro gửi ở kênh trái; không để hở chân này |
| WS | D25 | |
| SCK | D26 | |
| SD | D33 | Dữ liệu vào ESP32 |
Khối I2S của ESP32 gán được vào hầu hết các chân, nên có thể đổi ba hằng CHAN_SCK, CHAN_WS, CHAN_SD. Tránh GPIO34–39 cho SCK và WS (chỉ nhận vào), tránh GPIO6–11 (nối bộ nhớ flash). Giữ dây SCK, WS, SD ngắn: xung 1 MHz qua dây dài dễ méo, đọc sai bit.
4. dBFS và dB SPL: đổi số đo ra mức âm thanh

Mỗi khối 256 mẫu (16 ms), chương trình bỏ phần một chiều, tính giá trị hiệu dụng (căn của trung bình bình phương) rồi đổi ra decibel so với mức lớn nhất:
dBFS = 20 × log10(hiệu dụng / 8 388 608)
dBFS luôn âm hoặc bằng 0: 0 dBFS là micro đã đạt mức lớn nhất. Datasheet INMP441 cho biết tiếng ở 94 dB SPL (áp suất 1 Pa) cho ra −26 dBFS, nên dB SPL ≈ dBFS + 120. Con số này chỉ là ước tính: mỗi micro lệch vài dB, không lọc theo tai người (trọng số A) như máy đo độ ồn, và vỏ hộp, lỗ thu âm cũng làm thay đổi kết quả. Muốn so độ ồn giữa các nơi thì đủ dùng; muốn đo đúng theo quy chuẩn thì phải dùng máy đo đã kiểm định.
Nhiễu riêng của INMP441 vào khoảng 33 dBA (khoảng −87 dBFS), nên phòng thật yên tĩnh sẽ đọc ra gần mức đó chứ không thấp hơn.
5. Nhận tiếng vỗ tay

Tiếng vỗ tay khác tiếng nói ở chỗ nó nổ lên tức thì rồi tắt trong vài chục mili giây; tiếng nói lên dần qua vài khối. Chương trình dùng ba điều kiện:
- Mức nền là đường đi theo lúc yên tĩnh: hạ nhanh khi âm thanh nhỏ đi, tăng rất chậm khi có tiếng (mỗi khối chỉ nhích 0,5% khoảng cách), nên tiếng nói hay tiếng vỗ không kéo nền lên ngay.
- Một khối được coi là tiếng vỗ khi nó cao hơn khối trước ít nhất 12 dB và cao hơn nền ít nhất 20 dB.
- Sau một tiếng vỗ, bỏ qua 150 ms để tiếng vọng không bị đếm thêm.
Để bật tắt đèn, cần hai tiếng vỗ cách nhau 0,2–0,8 giây. Tiếng cửa đóng, tiếng gõ bàn, âm "p", "t" nói sát micro cũng có thể vọt lên đủ nhanh để bị nhận là một tiếng vỗ; yêu cầu hai tiếng liên tiếp đúng nhịp giảm hẳn những lần bật nhầm như vậy.
6. Chương trình
Chương trình dùng thư viện ESP_I2S có trong gói esp32 3.x, không cần cài thêm. Đặt VE_DO_THI = true để in mức âm và mức nền cho Serial Plotter, tiện chỉnh hai ngưỡng 12 dB, 20 dB theo căn phòng.
// PYWORLD.VN - Micro I2S INMP441 với ESP32: đo mức âm thanh (dBFS, ước tính dB SPL) và vỗ tay hai lần để bật tắt đèn
// Nối: VDD 3V3, GND, L/R xuống GND, SCK vào GPIO26, WS vào GPIO25, SD vào GPIO33. Đèn xanh trên bo (GPIO2) là đèn được bật tắt
// Chỉ dùng thư viện ESP_I2S có sẵn trong gói esp32
#include <ESP_I2S.h>
const int CHAN_SCK = 26, CHAN_WS = 25, CHAN_SD = 33;
const int CHAN_DEN = 2;
const uint32_t TAN_SO_MAU = 16000;
const int SO_MAU_KHOI = 256; // mỗi khối 16 ms
const bool VE_DO_THI = false; // true: in mức âm cho Serial Plotter
const float DBFS_SANG_SPL = 120.0; // datasheet: 94 dB SPL cho ra -26 dBFS → cộng 120
I2SClass i2s;
int32_t dem[SO_MAU_KHOI * 2]; // khung stereo: trái, phải, trái, phải...
int kenh = 0; // 0: trái (L/R nối GND), 1: phải (L/R nối 3V3)
float dc = 0; // phần một chiều của micro
float nen = -60, dbTruoc = -90; // mức nền theo dõi chậm, mức của khối trước (dBFS)
uint32_t lanVoTruoc = 0, lanIn = 0;
bool choLanHai = false, den = false;
int docKhoi() {
size_t n = i2s.readBytes((char *)dem, sizeof(dem));
return n / 8; // 8 byte cho mỗi khung (2 kênh × 32 bit)
}
// Kênh không có micro đọc ra số không đổi; kênh có micro luôn có nhiễu nền: chọn kênh dao động nhiều hơn.
void chonKenh() {
double tong[2] = {0, 0}, tong2[2] = {0, 0};
int dem2 = 0;
for (int k = 0; k < 20; k++) {
int n = docKhoi();
for (int i = 0; i < n; i++) {
for (int c = 0; c < 2; c++) {
double x = dem[2 * i + c] >> 8;
tong[c] += x;
tong2[c] += x * x;
}
}
dem2 += n;
}
if (dem2 == 0) return;
double ps0 = tong2[0] / dem2 - pow(tong[0] / dem2, 2), ps1 = tong2[1] / dem2 - pow(tong[1] / dem2, 2);
kenh = ps1 > ps0 ? 1 : 0;
}
void voTay(uint32_t bayGio) {
if (choLanHai && bayGio - lanVoTruoc >= 200 && bayGio - lanVoTruoc <= 800) {
den = !den; // lần vỗ thứ hai trong 0,2–0,8 giây: đảo trạng thái đèn
digitalWrite(CHAN_DEN, den);
choLanHai = false;
if (!VE_DO_THI) Serial.printf("Vo tay 2 lan: den %s\n", den ? "BAT" : "TAT");
} else {
choLanHai = true;
if (!VE_DO_THI) Serial.println("Vo tay 1 lan, cho lan thu hai...");
}
lanVoTruoc = bayGio;
}
void setup() {
Serial.begin(115200);
pinMode(CHAN_DEN, OUTPUT);
i2s.setPins(CHAN_SCK, CHAN_WS, -1, CHAN_SD);
if (!i2s.begin(I2S_MODE_STD, TAN_SO_MAU, I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO)) {
Serial.println("Khong khoi dong duoc I2S");
while (true) delay(1000);
}
chonKenh();
if (!VE_DO_THI) Serial.printf("INMP441 san sang, doc kenh %s. Vo tay 2 lan de bat tat den\n", kenh ? "phai" : "trai");
}
void loop() {
int n = docKhoi();
if (n == 0) return;
double tong2 = 0;
for (int i = 0; i < n; i++) {
float x = dem[2 * i + kenh] >> 8; // 24 bit có dấu nằm ở 24 bit cao của khung 32 bit
dc += (x - dc) * 0.001f; // trung bình trượt rất chậm: phần một chiều
float y = x - dc;
tong2 += (double)y * y;
}
float rms = sqrt(tong2 / n);
float dbfs = 20 * log10f(rms / 8388608.0f + 1e-9f); // so với mức lớn nhất 2^23
if (dbfs < -100) dbfs = -100; // khối toàn số 0 lúc mới chạy: không để mức nền tụt quá sâu
// Mức nền: xuống nhanh khi yên tĩnh, lên rất chậm khi có tiếng (để tiếng nói, tiếng vỗ không kéo nền lên ngay)
nen += (dbfs - nen) * (dbfs < nen ? 0.1f : 0.005f);
uint32_t bayGio = millis();
// Tiếng vỗ tay: vọt lên ít nhất 12 dB so với khối trước (16 ms) và cao hơn nền 20 dB
if (dbfs - dbTruoc >= 12 && dbfs - nen >= 20 && bayGio - lanVoTruoc > 150) voTay(bayGio);
if (choLanHai && bayGio - lanVoTruoc > 800) choLanHai = false;
dbTruoc = dbfs;
if (VE_DO_THI) {
Serial.printf("muc:%.1f,nen:%.1f\n", dbfs, nen);
} else if (bayGio - lanIn >= 500) {
lanIn = bayGio;
Serial.printf("Muc am %.1f dBFS (khoang %.0f dB SPL) | nen %.1f dBFS\n", dbfs, dbfs + DBFS_SANG_SPL, nen);
}
}
i2s.begin(I2S_MODE_STD, 16000, I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO): chuẩn I2S Philips, 32 bit mỗi kênh, hai kênh. Bộ đệmdemchứa xen kẽ trái, phải.chonKenh()đọc 20 khối đầu, tính phương sai từng kênh và chọn kênh lớn hơn. Các khối đầu tiên sau khi khởi động có thể toàn số 0, cũng được bỏ qua ở bước này.- Phần một chiều được bỏ bằng trung bình trượt rất chậm (
dc += (x − dc) × 0,001); mức dưới −100 dBFS bị chặn lại để mức nền không tụt quá sâu khi gặp khối toàn số 0. voTay()giữ trạng thái "đang chờ lần hai"; quá 0,8 giây không có lần hai thì bỏ.
7. Kết quả mong đợi

Phòng làm việc bình thường thường đọc ra khoảng 40–55 dB SPL; số trong hình là ví dụ. Vỗ tay hai lần cách micro khoảng 1 m, đèn xanh trên bo ESP32 đổi trạng thái. Muốn điều khiển đèn thật, nối module relay vào một chân khác và xem cách đấu an toàn trong bài ESP32 điều khiển module relay.
8. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Mức âm luôn −100 dBFS | Sai chân SD; micro không có nguồn; nối nhầm SCK với WS | Kiểm tra lại 6 dây; đo 3,3 V ở VDD |
| Mức âm gần 0 dBFS liên tục, nghe như nhiễu trắng | Lệch nhịp I2S: dây quá dài, SCK và WS đảo | Dây ngắn; đổi lại SCK, WS |
| Mức âm không đổi khi nói to | Đọc nhầm kênh trống; lỗ thu âm bị dán kín | Nối chắc L/R xuống GND rồi khởi động lại; để hở lỗ thu âm |
| Vỗ tay không nhận | Phòng ồn, nền cao; vỗ xa micro | Bật VE_DO_THI xem mức; hạ ngưỡng 20 dB xuống 15 dB |
| Đèn tự bật tắt khi nói chuyện | Âm bật hơi sát micro; tiếng gõ bàn | Đặt micro xa người nói; tăng ngưỡng 12 dB lên 15 dB |
| "Khong khoi dong duoc I2S" | Chân đã dùng cho việc khác, chân chỉ nhận vào | Chọn chân khác cho SCK, WS |
9. Xem thêm
- Tổng quan các chuẩn giao tiếp cho vi điều khiển: so I2S với UART, I2C, SPI.
- PWM trên ESP32: cho đèn sáng theo mức âm thanh thay vì chỉ bật tắt.
- Bài dải LED WS2812B trong cùng loạt module phổ biến: ghép với micro để làm đèn nháy theo nhạc.
Xem thêm