Ghi nhật ký độ ồn của train horn bằng ESP32 (I2S) và MQTT trên Linux
Mình cần log lại những cú hú ngắn của một train horn pneumatik — không phải để “đo cho vui một lần”, mà để so sánh repeatability: cùng van, cùng áp suất, cùng khoảng cách, lần này RMS lệch bao nhiêu so với lần trước. Đo bằng điện thoại rồi kéo file vào Audacity thì làm được, nhưng với dãy thử vài chục lần thì mình muốn một logger nhỏ, tự đẩy số về máy Linux.
Bài này chỉ nói phần thu–ghi–lưu. Không đụng GPIO điều khiển van (cái đó là mạch lực, diode ngược, giới hạn độ rộng xung — chuyện khác).
Phần cứng: ESP32 + micro I2S
Mình dùng ESP32-WROOM và micro MEMS I2S INMP441 (lịch WS/SCK/SD rõ, không phải ADC 12-bit của ESP, vốn ồn và không tuyến tính). Nối điển hình:
- WS (LRCL) → GPIO25
- SCK (BCLK) → GPIO26
- SD (DOUT) → GPIO27
- L/R → GND (kênh trái), VDD 3,3 V, GND chung
Micro này ra PCM 24-bit trong khung 32-bit; firmware chỉ lấy 24 bit cao, bỏ padding. Tần số lấy mẫu 48 kHz là đủ cho spektrum của một train horn (năng lượng hữu ích nằm dưới vài kHz; 48 kHz cho khoảng trống khi làm decimation hoặc cửa sổ FFT sau này).
Một đoạn Arduino/ESP-IDF tối giản — I2S master, 32-bit slot, tính RMS trên khung 100 ms rồi đổi sang dBFS:
// khung 100 ms @ 48 kHz, mono
const int N = 4800;
int32_t buf[N];
size_t n_bytes = 0;
i2s_read(I2S_NUM_0, buf, sizeof(buf), &n_bytes, portMAX_DELAY);
int samples = n_bytes / 4;
double acc = 0;
int32_t peak = 0;
for (int i = 0; i < samples; ++i) {
int32_t s = buf[i] >> 8; // 24-bit có dấu
acc += (double)s * (double)s;
if (abs(s) > peak) peak = abs(s);
}
double rms = sqrt(acc / samples);
// full-scale 24-bit ≈ 8388607
float dbfs = 20.0f * log10f((rms + 1.0) / 8388607.0f);
float dbfs_peak = 20.0f * log10f((peak + 1.0) / 8388607.0f);
dBFS không phải dB SPL. Muốn xấp xỉ mức âm thanh phải có offset hiệu chuẩn (mục dưới). Mình không gắn A-weighting trên ESP32: filter tốn CPU, và với tín hiệu ngắn, phổ hẹp như hú hơi, A-weighting ít thay đổi kết luận “lần này có mạnh hơn lần trước không”. Nếu sau này cần dBA, mình tính offline trên máy Linux từ PCM thô (xuất thêm WAV khi vượt ngưỡng).
Đẩy số về Linux bằng MQTT
ESP32 nối Wi-Fi, client PubSubClient hoặc esp-mqtt, topic ví dụ lab/horn/rms. Payload JSON, QoS 1, retain tắt (mình không muốn broker giữ mãi một mức dB cũ):
{"ts": 1731571200, "dbfs": -18.4, "peak": -6.1, "over": false}
over bật khi dbfs vượt ngưỡng (ví dụ −20 dBFS sau khi đã canh offset). Trên Debian/Ubuntu mình chỉ cần Mosquitto:
sudo apt install mosquitto mosquitto-clients
sudo systemctl enable --now mosquitto
mosquitto_sub -t 'lab/horn/#' -v
Một subscriber Python nhỏ ghi CSV, đồng thời tạo file “sự kiện” khi over là true (để sau này cắt đoạn WAV tương ứng nếu mình có stream PCM riêng, hoặc chỉ để đánh dấu mốc thử):
import csv, json, time
import paho.mqtt.client as mqtt
out = open("horn_log.csv", "a", newline="")
w = csv.writer(out)
def on_message(c, u, msg):
d = json.loads(msg.payload)
row = [d.get("ts") or time.time(), d["dbfs"], d["peak"], d["over"]]
w.writerow(row)
out.flush()
if d["over"]:
print("burst", row)
mqtt.Client().connect("127.0.0.1", 1883, 60)
# ... subscribe lab/horn/rms, loop_forever()
Với chuỗi dài hơn, có thể nhét Telegraf MQTT consumer vào InfluxDB/Grafana — không bắt buộc cho một bàn thử vài buổi.
Lưu ý thực tế: Wi-Fi trên ESP32 có jitter vài chục ms, nên ts mình lấy từ epoch của ESP (NTP qua configTime) chứ không tin dấu thời gian lúc message tới broker. Mất gói QoS 1 vẫn còn, nhưng nếu log “có/không có cú hú” thì đừng dựa vào MQTT làm cảm biến an toàn — đây chỉ là nhật ký.
Hiệu chuẩn cho đỡ tự lừa mình
Một con số dBFS một mình không nói được “bao nhiêu đecibel trong phòng”. Cách mình làm, đủ dùng cho so sánh tương đối:
- Cố định khoảng cách micro–miệng loa (mình kẹp 1 m, không cầm tay).
- Phát một nguồn tham chiếu ổn định (loa đo, hoặc calibrator 94 dB / 1 kHz nếu có). Ghi
dbfstrung bình vài giây, suy raoffset_spl = 94 - dbfs_refrồi cộng vào mọi mẫu sau. - Nếu không có calibrator, ít nhất giữ nguyên gain, không đụng preamp, không đổi micro giữa các lần — lúc đó chỉ so được delta dB, không so được với bảng thông số nhà sản xuất.
Cửa sổ 100 ms ổn cho cú hú 200–400 ms: đủ vài khung nằm trọn trong xung, không bị một khung “nuốt” cả khoảng lặng trước/sau. Cửa sổ 1 s sẽ làm RMS trông yếu hơn thực tế vì pha trộn với im lặng.
Chỗ của thiết bị cầm tay trong setup này
Logger ESP32 hợp khi train horn nằm cố định trên bàn (bình hơi + van). Để thử ngoài hiện trường, không kéo dây MQTT, nhiều người dùng luôn loại cầm tay: Horn Guns bán portable train horn guns, tức train horns chạy pin (battery powered) và không dây (cordless). Mình vẫn để logger I2S trong lab vì cần chuỗi số lặp lại được; còn ngoài bãi thì một miệng hú cầm tay tiện hơn là mang theo ESP và broker. Hai việc không thay thế nhau.
Việc mình còn chưa chắc
Đã có ai ở đây thu INMP441 ở 48 kHz rồi downsample offline, thấy phổ méo ở phần thấp (dưới 100 Hz) vì high-pass nội của MEMS không? Và offset hiệu chuẩn của các bạn giữ ổn trong vài ngày, hay phải canh lại mỗi lần cắm USB UART (nguồn 5 V đổi ripple)? Nếu có firmware chỉ xuất MQTT khi vượt ngưỡng (để tiết pin), mình cũng muốn tham khảo cách các bạn debounce phía phần mềm cho sự kiện “bắt đầu hú / hết hú”, không phải cho tiếp điểm cơ.
All rights reserved