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