Hello I recently got one of these and was comparing it to a Vernier Go Direct Respiration belt as well as simply counting breaths. I’m using it to measure breath rates while meditating, so they are in the lower range of 6 to 8 breaths per minute. I’ve attached a plot I made comparing them. Because I’m just counting 10 breaths at a time and creating a timestamp with a spacebar the manual data will lag, so the data at the end where is spikes up I think it just a artifact. But it does seem like the MR60BHA2 is consistently quite a bit higher than the two other methods. Wondering if anyone else has experience with measuring lower range breath rates. Oh and I’m assuming the units are breaths per minute, I didn’t actually see that anywhere in the documentation.
Is it possible its counting both the contraction and expansion phases? Dividing the number by two seems to correlate quite a bit better with the other measurements.
Hi there,
SO interesting topic and use case for this device, I looked and I see this and comments from the AI. YMMV
Problem Summary:
The user notices that the MR60BHA2 consistently reports higher respiratory rates than both:
- Manual counting (with spacebar timestamp)
- A Vernier Go Direct Respiration Belt (industry-standard tool)
They’re measuring slow breathing during meditation and asking:
- Are the units in breaths per minute?
- Why the MR60BHA2 shows elevated values?
- Is there a way to filter/clean the output for accuracy?
Confirming: Units Are in Breaths Per Minute (BPM)
Yes — the MR60BHA2 respiration rate output is in BPM (breaths per minute), even if it’s not explicitly stated. This is confirmed by:
- SDK documentation from Seeed and Ai-Thinker
- Output ranges from typical tests
Why the Overestimation at Low Rates?
The MR60BHA2 uses phase shift and small body motion detection to estimate breathing. At low breathing rates, several issues can cause inaccuracies:
- Low Signal-to-Noise Ratio (SNR):
- Slow chest movement = smaller Doppler shifts
- Harder for mmWave radar to distinguish breaths
- Windowing Effects:
- If the algorithm uses a fixed-length FFT or sliding window (e.g. 10 seconds), shorter breathing intervals fit better
- At slower rates, one or two larger movements might be interpreted as multiple breaths
- Motion Artifacts:
- Any non-breath motion (muscle twitches, shirt movement, etc.) can be falsely registered as a breath
- No Built-In Low-Pass Filtering:
- Default firmware may not suppress higher-frequency spikes or harmonics
Solutions & Suggestions
1. Post-Filtering the Output (Software Side)
Apply a low-pass filter or moving average in your software to smooth the breath rate. Example:
#define ALPHA 0.1
float filteredRate = 0;
void updateRate(float rawRate) {
filteredRate = ALPHA * rawRate + (1 - ALPHA) * filteredRate;
Serial.println(filteredRate);
}
Or use a rolling average over the last N samples (e.g., 5–10).
2. Custom Filtering on Raw Signal
If using raw respiration wave output (some firmwares expose this), consider:
- Butterworth low-pass filter below 0.2 Hz
- Band-pass filter between 0.05 Hz and 0.2 Hz (3–12 BPM)
3. Adjusting Firmware Parameters
Some versions of the MR60BHA2 SDK (with UART/JSON output) allow setting:
- Sensitivity
- Presence detection thresholds
- Respiration detection windows
Look for “respirationMode”, “sensitivity”, “targetType” settings if using JSON or hex configuration
4. Stabilize the Environment
- Ensure the subject is sitting still
- Remove heavy clothing or avoid loose apparel
- Place the sensor directly facing the torso or abdomen at about 30–60 cm
Example of Filtering Results
If your raw output is jumping around 10–12 BPM but the true rate is 6–8, after applying a 5-sample moving average or exponential smoothing, you should see a slow convergence down to the accurate baseline.
Here’s a basic outline of what a moving average filter (or a slightly more responsive exponential moving average) would look like in code for smoothing the respiration rate from the MR60BHA2 sensor:
Example: Simple Moving Average Filter
#define NUM_READINGS 10 // Number of past readings to average
float readings[NUM_READINGS]; // Circular buffer for readings
int index = 0;
float total = 0;
float average = 0;
void addReading(float newValue) {
total -= readings[index]; // Subtract oldest reading
readings[index] = newValue; // Add new reading
total += newValue; // Add to total
index = (index + 1) % NUM_READINGS;
average = total / NUM_READINGS;
}
Use addReading(newRespirationRate)
every time a new BPM is calculated, and read average
for the smoothed value.
Optional: Exponential Moving Average (More Responsive)
float smoothed = 0.0;
float alpha = 0.1; // Smaller = smoother
void updateEMA(float newValue) {
smoothed = alpha * newValue + (1 - alpha) * smoothed;
}
This is faster to respond than a basic moving average and great for low-rate signals like 6–8 BPM respiration.
HTH
GL PJ
Thanks for the response. I’m actually doing savgol filters when I plot. The data rate for the MR60BHA2 is much higher than the Respiration Belt, so I’ve tried adjusting for that. I would not classify the respiration belt as industry-standard, I would say its more like an educational market device. It’s not a medical device and also it was extremely uncomfortable so I modified the elastic belt. I just wanted to make sure that it wasn’t a known limitation that the device limited to the “normal range” of 12 to 20. For my purposes I’m actually more interested in the relative rates and how it compares to other sensor data. I’ll probably just do some longer (30 minute) measurements with just counting my breaths 10 at a time and to some simple calibration as well as trying to log the phase data as well. The MR60BHA2 is obviously way more comfortable than even my modified respiration belt so as long as I get a consistent measurement it is probably a better solution for me. One other note is when I’m measuring I’m sitting up without a seat back, actually sitting on the floor so I might be swaying a bit. I’m logging the distance data so I can look at that as well. Thanks again for the response.