# MAX30102 心率 / 脈搏 / 血氧感測器

<figure><img src="https://1275793585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LaZQFBYOS3O0ksiEmR1%2Fuploads%2F7MZ1kSiMLnzadnSSl0Jl%2Fimage.png?alt=media&#x26;token=34b3a777-82e9-4323-b8c0-c7243c4b767f" alt="" width="137"><figcaption></figcaption></figure>

{% hint style="danger" %} <mark style="color:red;">**注意： MAX30102感測器針腳外漏，所以測量時手指需保持乾燥，太潮濕有可能造成感測器短路。**</mark>
{% endhint %}

### **氣體感測器模組電路圖**

* Raspberry Pi Pico W
* Raspberry Pi Pico W 擴充板
* MAX30102 心率 / 脈搏 / 血氧感測器
* 母 – 母 杜邦線

{% hint style="info" %}
*<mark style="color:$warning;">**MAX30102 心率 / 脈搏 / 血氧感測器是I2C訊號輸入。本範例之模組SDA腳位需接至Raspberry Pi Pico擴充板D4腳位，模組SCL腳位需接至Raspberry Pi Pico擴充板D5腳位。**</mark>*
{% endhint %}

<figure><img src="https://1275793585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LaZQFBYOS3O0ksiEmR1%2Fuploads%2FYZvF4j4fBgGF6rQGpsBN%2Fimage.png?alt=media&#x26;token=805a98ad-e503-4e7c-b4f3-450f6f0f8e8b" alt="" width="497"><figcaption></figcaption></figure>

### **Arduino 程式如下**

```
#include <Wire.h>
#include "MAX30105.h"
#include "heartRate.h"

// -------------- 參數設定 --------------
#define FINGER_ON 7000               // 手指是否放上的 IR 門檻
#define MINIMUM_SPO2 90.0            // 最低血氧值（避免顯示過低）
boolean max3010xReady = false;       // 感測器初始化狀態

// 心跳與血氧計算用的平均與平滑變數
double avgRed = 0, avgIR = 0, ESpO2 = MINIMUM_SPO2;
const double FSpO2 = 0.7, frate = 0.95;  // SpO2 與 DC 分量平滑係數
byte validMin = 20, validMax = 250;      // 合理的心跳範圍（20 ~ 250 BPM）

MAX30105 max3010xSensor;  // 建立 MAX30105 感測器物件

// -------------- 心跳偵測函式 --------------
int getHeartBeat(byte avgTimes) {
  long lastBeat = 0, myIRvalue = 0, delta = 0;
  byte myCount = 0;
  int beatSum = 0;
  float myBPM = 0.0;

  // 收集指定次數的心跳數據並平均
  while (myCount < avgTimes) {
    myIRvalue = max3010xSensor.getIR();
    if (checkForBeat(myIRvalue)) {
      delta = millis() - lastBeat;
      lastBeat = millis();
      myBPM = 60 / (delta / 1000.0);  // 換算成 BPM

      if (myBPM < validMax && myBPM > validMin) {
        beatSum += (byte)myBPM;
        myCount++;
      }
    }
    // 偵測手指離開則中止
    if (myIRvalue < FINGER_ON)
      break;
  }

  if (myCount == 0) myCount = 1;  // 防止除以 0
  return (beatSum / myCount);     // 傳回平均心跳
}

// -------------- 血氧計算函式 --------------
double getSPO2(byte avgTimes) {
  uint32_t ir, red;
  double df_red, df_ir;
  byte myCount = 0;
  double sumIR = 0, sumRed = 0, SpO2 = 0;

  // 讀取指定次數的 Red 與 IR 數據，計算 AC 成分變異
  while (myCount < avgTimes) {
    max3010xSensor.check();
    if (max3010xSensor.available()) {
      myCount++;
      red = max3010xSensor.getFIFOIR();   // 取得 IR 值
      ir = max3010xSensor.getFIFORed();   // 取得紅光值
      max3010xSensor.nextSample();

      df_red = (double)red;
      df_ir = (double)ir;

      // 平滑 DC 成分
      avgRed = avgRed * frate + df_red * (1.0 - frate);
      avgIR = avgIR * frate + df_ir * (1.0 - frate);

      // 累積 AC 成分變異（平方）
      sumRed += (df_red - avgRed) * (df_red - avgRed);
      sumIR += (df_ir - avgIR) * (df_ir - avgIR);
    }
  }

  // 計算 R 比例並推估 SpO2
  if (myCount == avgTimes) {
    double R = (sqrt(sumRed) / avgRed) / (sqrt(sumIR) / avgIR);

    // 用較穩定的 SpO2 經驗公式
    SpO2 = 110.0 - 25.0 * R;

    // 平滑顯示數值
    ESpO2 = FSpO2 * ESpO2 + (1.0 - FSpO2) * SpO2;

    // 限制範圍
    if (ESpO2 <= MINIMUM_SPO2) ESpO2 = MINIMUM_SPO2;
    if (ESpO2 > 100)           ESpO2 = 99.9;
  } else {
    ESpO2 = MINIMUM_SPO2;
  }

  return ESpO2;  // 傳回平滑後 SpO2
}

// -------------- 初始化設定 --------------
void setup() {
  max3010xReady = max3010xSensor.begin(Wire, I2C_SPEED_FAST);

  // 感測器參數設定（亮度、平均數、取樣率、脈衝寬度、解析度）
  max3010xSensor.setup(127, 4, 2, 800, 215, 16384);
  max3010xSensor.enableDIETEMPRDY();  // 啟用內建溫度
  max3010xSensor.setPulseAmplitudeRed(0x0A);  // 紅光亮度
  max3010xSensor.setPulseAmplitudeGreen(0);   // 不使用綠光

  // 心跳有效範圍（可以依年齡或應用調整）
  validMin = 20;
  validMax = 250;

  Serial.begin(9600);  // 設定序列埠傳輸速度
}

// -------------- 主迴圈（持續讀取與輸出） --------------
void loop() {
  // 判斷是否有手指放上去（根據 IR 值）
  if ((max3010xSensor.getIR() > FINGER_ON)) {
    Serial.println((String("Heart Beat:") + String(getHeartBeat(10))));
    Serial.println((String("SpO2 :") + String(getSPO2(30))));
  } else {
    // 沒偵測到手指，清除狀態並顯示提示
    Serial.println("NO finger detected!!");
    avgRed = 0;
    avgIR = 0;
    ESpO2 = MINIMUM_SPO2;
  }
}
```

### **程式執行結果**

MAX30102 模組是由紅外線來偵測是否有手指，若無手指按壓則會顯示"No finger detected!!" 。

<figure><img src="https://1275793585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LaZQFBYOS3O0ksiEmR1%2Fuploads%2FpCLxG6OmHmVyiF6OMCm1%2Fimage.png?alt=media&#x26;token=f2ca5938-6a9d-4eef-8b28-e02dda74ce7f" alt="" width="341"><figcaption></figcaption></figure>

將手指放置於MAX30102 模組後，盡量不要移動手指，也避免身體做過大的動作，以免讓數值有誤差。

若偵測到手指，則會顯示心跳值及血氧值。

<figure><img src="https://1275793585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LaZQFBYOS3O0ksiEmR1%2Fuploads%2F6sS5azIVwsCtizkTANv8%2Fimage.png?alt=media&#x26;token=42bce402-a237-4eb6-9577-58e51417f749" alt="" width="350"><figcaption></figcaption></figure>
