Hello,
I would like to detect a beep of a specific frequency using the MSM381ACT001 microphone on the Seeed Studio XIAO MG24. Do you think this is technically possible with the component?
Regards
Hello,
I would like to detect a beep of a specific frequency using the MSM381ACT001 microphone on the Seeed Studio XIAO MG24. Do you think this is technically possible with the component?
Regards
interesting project
yes⊠I believe the hardware would be capable of this task⊠the proof is in the software
Hi there,
I would say yes, but only given certain parameters. What is the Frequency you want to detect? WIll it be a constant amplitude and close up or FAR away (these micâs donât do long range vs, fidelity
Given a constant tone or even a BEEP pulse, and constant distance to the source at a fixed volume level. VERY doable.
There is an Arduino Example MIC , with a TFFT display. look at that example and others to get a feel on how the mic performs in YOUR situation. to get the most reliable and repeatable outcome.
There is a algorithm ro filter called a âGoertzelâ itâs what you use for this type of detection. O(N) per block, tiny RAM/CPU, Tunable: just change
đ0 sample rate, or frame length, it is very Robust under noise with proper thresholds. it is a "a digital signal processing technique for efficiently calculating a specific frequency component of a Discrete Fourier Transform (DFT) "
https://docs.arduino.cc/libraries/goertzel/
check the basics here;
and here is a basic starting point that compiles AOK
// ============================================================
// XIAO MG24: Beep Detector (Goertzel) for analog MEMS mic
// Revision: 1.0 (2025-09-29)
// Target tone: 2000 Hz @ 8 kHz sample rate
// ============================================================
#include <Arduino.h>
// ---- User config ----
static const uint8_t MIC_PIN = A0; // Your ADC pin
static const float FS = 8000.0; // Sample rate (Hz)
static const uint16_t N = 205; // Samples per frame (~25.6 ms)
static const float F0 = 2000.0; // Target frequency (Hz)
static const uint8_t FRAMES_HYST = 3; // Require this many consecutive "hits"
static const float DETECT_MULT = 8.0; // Threshold = noise_floor * DETECT_MULT
// ---- Derived Goertzel coeff ----
static float coeff;
static float omega;
static float sine_;
static float cosine_;
// ---- State ----
static float noise_floor = 0.0f;
static uint8_t consecutive_hits = 0;
// Optional: simple DC removal (high-pass) parameters
static const float HP_ALPHA = 0.995f; // closer to 1.0 => more aggressive DC tracking
static float dc_est = 0.0f;
inline float goertzel_run(const int16_t *x, uint16_t len) {
float s_prev = 0.0f;
float s_prev2 = 0.0f;
for (uint16_t i = 0; i < len; i++) {
float s = x[i] + coeff * s_prev - s_prev2;
s_prev2 = s_prev;
s_prev = s;
}
float real = s_prev - s_prev2 * cosine_;
float imag = s_prev2 * sine_;
return real * real + imag * imag; // power at F0
}
void setup_goertzel() {
// k = round(N * f0 / fs)
float k = roundf((N * F0) / FS);
omega = (2.0f * PI * k) / N;
sine_ = sinf(omega);
cosine_ = cosf(omega);
coeff = 2.0f * cosine_;
}
void calibrate_noise_floor(uint16_t frames = 30) {
Serial.println(F("[Cal] Measuring noise floor..."));
int16_t buf[N];
float acc = 0.0f;
for (uint16_t f = 0; f < frames; f++) {
// collect a frame
uint32_t t0 = micros();
for (uint16_t i = 0; i < N; i++) {
// hold precise sampling interval
uint32_t target = t0 + (uint32_t)((i * (1000000.0f / FS)));
while ((int32_t)(micros() - target) < 0) { /* wait */ }
int raw = analogRead(MIC_PIN); // 12-bit typical
// map to signed centered around mid-scale (assume ~2048 mid)
// You can tweak midpoint if you know your bias exactly.
int16_t s = (int16_t)raw - 2048;
// DC removal (simple one-pole)
dc_est = HP_ALPHA * dc_est + (1.0f - HP_ALPHA) * s;
float s_hp = s - dc_est;
buf[i] = (int16_t)s_hp; // store filtered
}
float p = goertzel_run(buf, N);
acc += p;
}
noise_floor = (acc / frames);
Serial.print(F("[Cal] Noise floor (power): "));
Serial.println(noise_floor, 2);
}
void setup() {
Serial.begin(115200);
delay(200);
Serial.println(F("\n=== XIAO MG24 Beep Detector (Goertzel) ==="));
Serial.println(F("Mic: MSM381ACT001 -> A0, fs=8kHz, N=205, f0=2kHz"));
analogReadResolution(12); // If supported by MG24 core (usually 12 bits)
// analogReference(...) // set if your core supports and you need a known Vref
setup_goertzel();
calibrate_noise_floor(30);
}
void loop() {
static int16_t buf[N];
// Collect one frame at ~exact FS using busy-wait timing
uint32_t t0 = micros();
for (uint16_t i = 0; i < N; i++) {
uint32_t target = t0 + (uint32_t)((i * (1000000.0f / FS)));
while ((int32_t)(micros() - target) < 0) { /* wait */ }
int raw = analogRead(MIC_PIN);
int16_t s = (int16_t)raw - 2048;
// DC removal
dc_est = HP_ALPHA * dc_est + (1.0f - HP_ALPHA) * s;
float s_hp = s - dc_est;
buf[i] = (int16_t)s_hp;
}
// Compute power at F0
float power_f0 = goertzel_run(buf, N);
// Simple neighbor guard (optional): compute at F0+Îf to ensure narrowband tone
// Îf ~ FS/N; here ~39 Hz. We can skip for first pass to keep CPU low.
// Detection
float threshold = max(noise_floor * DETECT_MULT, noise_floor + 1.0f);
bool hit = (power_f0 > threshold);
// Debounce with consecutive frames
if (hit) {
consecutive_hits++;
} else if (consecutive_hits > 0) {
consecutive_hits--;
}
bool detected = (consecutive_hits >= FRAMES_HYST);
// Print a light debug stream
static uint32_t lastPrint = 0;
if (millis() - lastPrint > 200) {
lastPrint = millis();
Serial.print(F("P:"));
Serial.print(power_f0, 1);
Serial.print(F(" Th:"));
Serial.print(threshold, 1);
Serial.print(F(" NF:"));
Serial.print(noise_floor, 1);
Serial.print(F(" Hits:"));
Serial.print(consecutive_hits);
Serial.print(F(" "));
if (detected) Serial.print(F("DETECTED"));
Serial.println();
}
// Optional: periodically refresh noise floor when not detecting
static uint32_t lastRecal = 0;
if (!detected && (millis() - lastRecal > 5000)) {
lastRecal = millis();
// compute quick running noise estimate from last power
// (conservative: exponential average)
noise_floor = 0.95f * noise_floor + 0.05f * power_f0;
}
}
here is a basic starting point with some suggestions as well.
This will:
Sample A0 at ~8 kHz using a tight timing loop (good enough to start).
Detect a 2 kHz beep.
Print rolling magnitudes and a clean âDETECTEDâ line when the tone is present.
Includes a quick noise-floor calibration at boot.
If the mic is very quiet or your tone is far away, youâll need gain and a clean bias. Donât expect miracles from a raw high-impedance feed.
Timing with a busy-wait loop is âgood enoughâ to prove it works; for production, move sampling to a hardware timer + ADC DMA to get rock-solid FS
and free CPU.
If the beep can drift ± a few percent, two choices
Widen the detection by testing two or three close bins (F0âÎ, F0, F0+Î) and summing power
lower level and loose a little f S/N
The hardware can do it. you can use the serial plotter to see if itâs working also and use your cell phone to generate the tone burst.
HTH
GL PJ
I would like to detect the frequency of 4 kHz.
Thank you, Iâll look into all that.
Hi there,
Ok, So that is well in the range, change the F0 to 4000, you tweak it depending on the waveform your going after, frames and samples per frame are the fine tuning dialsâŠ
HTH
GL PJ
When I was working on voice detection, I used fft to make frequency buckets from audio samples.
You can assign frequency buckets value as a triggering point in code to take actions when that tone has sufficient amplitude.