pdf-icon

Arduino 上手教程

2. 设备开发 & 案例程序

5. 拓展模块

6. 应用案例

Hat Heart Arduino 使用教程

1. 准备工作

2. 注意事项

引脚兼容性
由于每款主机的引脚配置不同,使用前请参考产品文档中的引脚兼容表,并根据实际引脚连接情况修改案例程序。

3. 案例程序

  • 本教程中使用的主控设备为 StickS3,搭配 Hat Heart 模块。本热成像模块采用 I2C 方式通讯,根据实际的电路连接修改程序中的引脚定义,设备堆叠后对应的 I2C IO 为 G0 (SCL)G8 (SDA)
注意事项
1. 手指放上传感器后,请保持手指稳定并等待几十秒,以获得正确的 SpO2 和 BPM(心率)数值。
2. 若使用 StickS3 作为主控设备,由于硬件限制,按下复位按键后不能复位心率模块,需断电重启设备以重新初始化心率模块,否则主控设备会显示初始化失败的提示信息。
cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
#include <M5Unified.h>    
#include <Wire.h>      
#include "MAX30105.h"   
#include "heartRate.h"    // Heart rate detection algorithm
#include "spo2_algorithm.h" // SpO2 calculation algorithm

M5Canvas canvas = M5Canvas(&M5.Lcd);

MAX30105 Sensor;

#define Heart_SDA 8
#define Heart_SCL 0

// Length of sample buffer for SpO2 calculation
#define bufferLength   100

// Button A pin definition
const byte Button_A = 11;

// Buffers to store infrared (IR) and red light sensor data
uint32_t irBuffer[bufferLength];
uint32_t redBuffer[bufferLength];

// Button state and reset flag
int8_t ButtonState, flag_Reset;

// SpO2 and heart rate calculation results
int32_t spo2, heartRate, old_spo2;
int8_t validSPO2, validHeartRate;

// Heart rate averaging parameters
const byte RATE_SIZE = 5;     // Number of beats used for averaging
uint16_t rate_begin  = 0;     // Counter for initial averaging
uint16_t rates[RATE_SIZE];   // Circular buffer for BPM values
byte rateSpot = 0;            // Index for BPM buffer
float beatsPerMinute;         // Instantaneous BPM
int beatAvg;                  // Averaged BPM

// Failure counter (e.g., finger removed)
byte num_fail;

// Display waveform buffers (red & IR), width 320 samples
uint16_t line[2][320] = {0};

// Current write positions for waveform buffers
uint32_t red_pos = 0, ir_pos = 0;

// Max/min values for waveform scaling
uint16_t ir_max = 0, red_max = 0, ir_min = 0, red_min = 0;
uint16_t ir_last = 0, red_last = 0;

// Raw filtered values from last sample
uint16_t ir_last_raw = 0, red_last_raw = 0;

// Display-mapped waveform values
uint16_t ir_disdata, red_disdata;

// Low-pass filter coefficient (scaled by 256)
uint16_t Alpha = 0.3 * 256;

// Timing variables for beat detection
uint32_t t1, t2, last_beat, Program_freq;

/**
 * @brief Interrupt callback for Button A
 *        Sets reset flag when button is pressed
 */
void callBack(void) {
    ButtonState = digitalRead(Button_A); // Read button state
    if (ButtonState == 0) flag_Reset = 1; // Set reset flag if button pressed
    delay(10);                         // Simple debounce delay
}

void setup() {
    M5.begin();
    Serial.begin(115200);
    pinMode(Button_A, INPUT);
    Wire.begin(Heart_SDA, Heart_SCL);

    M5.Lcd.setRotation(3);
    M5.Lcd.setSwapBytes(false);
    canvas.createSprite(240, 135);
    canvas.setSwapBytes(true);
    canvas.createSprite(240, 135);

    // Initialize MAX30102/MAX30105 sensor
    if (!Sensor.begin(Wire, I2C_SPEED_FAST)) {
        M5.Lcd.print("Init Failed");
        Serial.println(F("MAX30102 was not found. Please check wiring/power."));
        while (1); // Halt program
    }

    // Prompt user to place finger on sensor
    Serial.println("Place your index finger on the Sensor with steady pressure");

    // Attach interrupt to Button A (falling edge)
    attachInterrupt(Button_A, callBack, FALLING);

    // Configure MAX30102 sensor with default settings
    Sensor.setup();
    Sensor.clearFIFO(); // Optional FIFO clear (commented out)
}

void loop() {
    uint16_t ir, red;

    // If reset flag is set, clear sensor FIFO
    if (flag_Reset) {
        Sensor.clearFIFO();
        delay(5);
        flag_Reset = 0;
    }

    // Main acquisition loop (runs until reset)
    while (flag_Reset == 0) {
        // Wait until sensor data is available
        while (Sensor.available() == false) {
            delay(10);
            Sensor.check();
        }

        // Continuous data reading loop
        while (1) {
            red = Sensor.getRed(); // Read red light value
            ir  = Sensor.getIR();  // Read infrared value

            // Check if finger is properly placed
            if ((ir > 1000) && (red > 1000)) {
                num_fail = 0;       // Reset failure counter
                t1 = millis();      // Timestamp before processing

                // Store samples into circular buffers
                redBuffer[(red_pos + 100) % 100] = red;
                irBuffer[(ir_pos + 100) % 100]   = ir;

                // Measure processing frequency
                t2 = millis();
                Program_freq++;

                // Heartbeat detection using IR signal
                if (checkForBeat(ir) == true) {
                    long delta =
                        millis() - last_beat - (t2 - t1) * (Program_freq - 1);
                    last_beat = millis();
                    Program_freq = 0;

                    // Calculate BPM
                    beatsPerMinute = 60 / (delta / 1000.0);

                    if (beatsPerMinute > 40 && beatsPerMinute < 180) {

                        if (rate_begin == 0) {
                            beatAvg = (int)beatsPerMinute;
                        }

                        rates[rateSpot++] = (uint16_t)beatsPerMinute;
                        rateSpot %= RATE_SIZE;

                        if (rate_begin < RATE_SIZE) rate_begin++;

                        int sum = 0;
                        for (byte i = 0; i < rate_begin; i++) {
                            sum += rates[i];
                        }
                        beatAvg = sum / rate_begin;
                    }
                }
            } else {
                num_fail++; // Increase failure count if finger not detected
            }

            // Apply low-pass filtering to waveform data
            line[0][(red_pos + 240) % 320] =
                (red_last_raw * (256 - Alpha) + red * Alpha) / 256;
            line[1][(ir_pos + 240) % 320] =
                (ir_last_raw * (256 - Alpha) + ir * Alpha) / 256;

            red_last_raw = line[0][(red_pos + 240) % 320];
            ir_last_raw  = line[1][(ir_pos + 240) % 320];

            // Advance waveform positions
            red_pos++;
            ir_pos++;

            // Exit loop if no more data or reset requested
            if ((Sensor.check() == false) || flag_Reset) break;
        }

        // Clear FIFO after processing batch
        Sensor.clearFIFO();

        // Find max and min values for waveform scaling
        for (int i = 0; i < 240; i++) {
            if (i == 0) {
                red_max = red_min = line[0][(red_pos + i) % 320];
                ir_max = ir_min = line[1][(ir_pos + i) % 320];
            } else {
                red_max = (line[0][(red_pos + i) % 320] > red_max)
                              ? line[0][(red_pos + i) % 320]
                              : red_max;
                red_min = (line[0][(red_pos + i) % 320] < red_min)
                              ? line[0][(red_pos + i) % 320]
                              : red_min;
                ir_max = (line[1][(ir_pos + i) % 320] > ir_max)
                             ? line[1][(ir_pos + i) % 320]
                             : ir_max;
                ir_min = (line[1][(ir_pos + i) % 320] < ir_min)
                             ? line[1][(ir_pos + i) % 320]
                             : ir_min;
            }
            if (flag_Reset) break;
        }

        // Clear display canvas
        canvas.fillRect(0, 0, 240, 135, BLACK);

        // Draw waveform lines
        for (int i = 0; i < 240; i++) {
            red_disdata =
                map(line[0][(red_pos + i) % 320], red_max, red_min, 0, 135);
            ir_disdata =
                map(line[1][(ir_pos + i) % 320], ir_max, ir_min, 0, 135);

            canvas.drawLine(i, red_last, i + 1, red_disdata, RED);
            canvas.drawLine(i, ir_last, i + 1, ir_disdata, BLUE);

            ir_last  = ir_disdata;
            red_last = red_disdata;

            if (flag_Reset) break;
        }

        // Save previous SpO2 value
        old_spo2 = spo2;

        // Calculate heart rate and SpO2 when enough samples are collected
        if (red_pos > 100)
            maxim_heart_rate_and_oxygen_saturation(
                irBuffer, bufferLength, redBuffer,
                &spo2, &validSPO2,
                &heartRate, &validHeartRate);

        // Keep old SpO2 if new value is invalid
        if (!validSPO2) spo2 = old_spo2;

        // Display text information
        canvas.setTextSize(1);

        canvas.setTextColor(RED);
        canvas.setCursor(5, 5);
        canvas.printf("red:%d,%d,%d", red_max, red_min, red_max - red_min);
        Serial.printf("red:%d,%d,%d, ", red_max, red_min, red_max - red_min);

        canvas.setTextColor(BLUE);
        canvas.setCursor(5, 15);
        canvas.printf("ir:%d,%d,%d", ir_max, ir_min, ir_max - ir_min);
        Serial.printf("ir:%d,%d,%d ", ir_max, ir_min, ir_max - ir_min);

        canvas.setCursor(5, 25);
        if (num_fail < 10) {
            canvas.setTextColor(GREEN);
            canvas.printf("spo2:%d,", spo2);
            canvas.setCursor(60, 25);
            canvas.printf(" BPM:%d", beatAvg);
            Serial.printf(" spo2:%d, BPM:%d\n", spo2, beatAvg);
        } else {
            canvas.setTextColor(RED);
            canvas.printf("No Finger!!");
            Serial.printf("No Finger!!\n");
        }

        // Push canvas to LCD
        canvas.pushSprite(0, 0);
    }
}

4. 心率检测

  • 设备上电后,屏幕上会显示从 MAX30102 传感器获取的原始红外和红光数据。将手指放在心率模块的传感器区域,可观察到屏幕上 SpO2 和 BPM(心率)数值及曲线的变化,请根据实际情况调整手指位置和压力,以获得稳定的读数。
On This Page