MR60BHA2 Breath Rates doesn't seem to go below 10 bpm

Hi there,

SO interesting topic and use case for this device, I looked and I see this and comments from the AI. YMMV :v:

:mag: 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?

:white_check_mark: 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

:brain: 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:

  1. Low Signal-to-Noise Ratio (SNR):
  • Slow chest movement = smaller Doppler shifts
  • Harder for mmWave radar to distinguish breaths
  1. 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
  1. Motion Artifacts:
  • Any non-breath motion (muscle twitches, shirt movement, etc.) can be falsely registered as a breath
  1. No Built-In Low-Pass Filtering:
  • Default firmware may not suppress higher-frequency spikes or harmonics

:hammer_and_wrench: 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

:chart_with_downwards_trend: 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:

:brain: 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.


:gear: 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 :slight_smile: PJ :v: