M-Design设计竞赛 - 基于MX30102的简易蓝牙心率计设计
该项目使用了arduino nano matter开发板和美信公司的MX30102心率血氧模块,实现了对心率测量并通过蓝牙BLE传输至手机侧进行查看的设计,它的主要功能为:把手指末端稳定放置在传感器上,能够测量用户的心率数据。并通过OLED屏幕进行展示。
标签
BLE
OLED
心率
M-Design设计竞赛
ARDUINO NANO MATTER
MX30102
littlestudent
更新2025-04-01
43

非常感谢贸泽和硬禾举办的本次M-Design设计竞赛活动,本次选择了推荐的方向二:安全监测中的“医疗检测”

具体完成的内容是

  • 基于主控Arduino Nano Matter开发板采集心率传感器MX30102数据
  • 基于u8g2图形库和硬件IIC接口驱动OLED屏幕,用于动态显示手指是否放下以及显示测量结果
  • 基于Arduino BLE协议栈,在采集到心率数据后,通过BLE传输至上位机进行心率数据的查看。

系统运行框图如下:

image.png


首先展示实物图:

从上之下分别是MX30102,OLED与ARDUINO NANO MATTER开发板。

image.png


后来重新设计了PCB扩展底板,从而避免了复杂的接线,并提高了小系统的稳定性。附件是立创EDA标准版的GEBER文件。


如下是简易扩展板的原理图:

image.png


如下是PCB:

image.png

3D预览:

image.png

实物组装:

image.png

一、项目框图

主控模块是Arduino Nano Matter开发板,可以方便的使用ARduino生态。Arduino Nano Matter 板采用 SiLabs MGM240SD22VNA 微控制器构建,该微控制器包含一个运行频率高达 78MHz 的 32 位 Arm Cortex-M33 内核,并具有数字信号处理(DSP)和浮点单元(FPU)加速功能。板载 256kB 静态随机存取存储器(SRAM)和 1536kB 大容量闪存,以及 IEEE 802.15.4 和蓝牙 5.3 低功耗(BLE)无线电模块,前者支持 Thread 等协议,后者支持蓝牙 Mesh。

这个板子具有两路I2C接口,因此可以分别驱动MX30102心率模块和I2C接口的OLED屏幕。


image.png




心率检测模块:

image.png

MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。它集成了一个红光LED和一个红外光LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路。MAX30102采用一个1.8V电源和一个独立的5.0V用于内部LED的电源,应用于可穿戴设备进行心率和血氧采集检测,佩戴于手指点耳垂和手腕处。标准的I2C兼容的通信接口可以将采集到的数值传输Arduino、KL25Z、STM32、STC51等单片机进行心率和血氧计算。


OLED屏幕:

image.png

具有GND, VCC, SCL, SDA四根引脚,I2C通信接口。注意7位地址是0X3C.


二、程序流程图

程序整体运行流程如下所示:

image.png

三、心率检测

MAX30102传感器的工作原理基于红外光在血液中的吸收特性。红外光能够穿透皮肤并被血液吸收,血液中的氧合血和脱氧血对红外光的吸收程度不同,因此可以通过测量红外光的吸收情况来推断血液的氧合状态。传感器利用LED发出的光照射到皮肤表面,然后通过光电检测器接收经过皮肤反射的光信号,并根据光信号的变化来计算心率和血氧饱和度。

这里核心就是光信号的获取:通过particleSensor.getIR()获取。


主要代码如下:

  long irValue = particleSensor.getIR();


  if (checkForBeat(irValue) == true) {
    //We sensed a beat!
    long delta = millis() - lastBeat;
    lastBeat = millis();


    beatsPerMinute = 60 / (delta / 1000.0);


    if (beatsPerMinute < 255 && beatsPerMinute > 20) {
      rates[rateSpot++] = (byte)beatsPerMinute;  //Store this reading in the array
      rateSpot %= RATE_SIZE;                     //Wrap variable


      //Take average of readings
      beatAvg = 0;
      for (byte x = 0; x < RATE_SIZE; x++)
        beatAvg += rates[x];
      beatAvg /= RATE_SIZE;
    }
  }


心率检测到完整代码:

int16_t IR_AC_Max = 20;
int16_t IR_AC_Min = -20;

int16_t IR_AC_Signal_Current = 0;
int16_t IR_AC_Signal_Previous;
int16_t IR_AC_Signal_min = 0;
int16_t IR_AC_Signal_max = 0;
int16_t IR_Average_Estimated;

int16_t positiveEdge = 0;
int16_t negativeEdge = 0;
int32_t ir_avg_reg = 0;

int16_t cbuf[32];
uint8_t offset = 0;

static const uint16_t FIRCoeffs[12] = {172, 321, 579, 927, 1360, 1858, 2390, 2916, 3391, 3768, 4012, 4096};

// Heart Rate Monitor functions takes a sample value and the sample number
// Returns true if a beat is detected
// A running average of four samples is recommended for display on the screen.
bool checkForBeat(int32_t sample)
{
bool beatDetected = false;

// Save current state
IR_AC_Signal_Previous = IR_AC_Signal_Current;

//This is good to view for debugging
//Serial.print("Signal_Current: ");
//Serial.println(IR_AC_Signal_Current);

// Process next data sample
IR_Average_Estimated = averageDCEstimator(&ir_avg_reg, sample);
IR_AC_Signal_Current = lowPassFIRFilter(sample - IR_Average_Estimated);

// Detect positive zero crossing (rising edge)
if ((IR_AC_Signal_Previous < 0) & (IR_AC_Signal_Current >= 0))
{

IR_AC_Max = IR_AC_Signal_max; //Adjust our AC max and min
IR_AC_Min = IR_AC_Signal_min;

positiveEdge = 1;
negativeEdge = 0;
IR_AC_Signal_max = 0;

//if ((IR_AC_Max - IR_AC_Min) > 100 & (IR_AC_Max - IR_AC_Min) < 1000)
if ((IR_AC_Max - IR_AC_Min) > 20 & (IR_AC_Max - IR_AC_Min) < 1000)
{
//Heart beat!!!
beatDetected = true;
}
}

// Detect negative zero crossing (falling edge)
if ((IR_AC_Signal_Previous > 0) & (IR_AC_Signal_Current <= 0))
{
positiveEdge = 0;
negativeEdge = 1;
IR_AC_Signal_min = 0;
}

// Find Maximum value in positive cycle
if (positiveEdge & (IR_AC_Signal_Current > IR_AC_Signal_Previous))
{
IR_AC_Signal_max = IR_AC_Signal_Current;
}

// Find Minimum value in negative cycle
if (negativeEdge & (IR_AC_Signal_Current < IR_AC_Signal_Previous))
{
IR_AC_Signal_min = IR_AC_Signal_Current;
}

return(beatDetected);
}

// Average DC Estimator
int16_t averageDCEstimator(int32_t *p, uint16_t x)
{
*p += ((((long) x << 15) - *p) >> 4);
return (*p >> 15);
}

// Low Pass FIR Filter
int16_t lowPassFIRFilter(int16_t din)
{
cbuf[offset] = din;

int32_t z = mul16(FIRCoeffs[11], cbuf[(offset - 11) & 0x1F]);

for (uint8_t i = 0 ; i < 11 ; i++)
{
z += mul16(FIRCoeffs[i], cbuf[(offset - i) & 0x1F] + cbuf[(offset - 22 + i) & 0x1F]);
}

offset++;
offset %= 32; //Wrap condition

return(z >> 15);
}

// Integer multiplier
int32_t mul16(int16_t x, int16_t y)
{
return((long)x * (long)y);
}


四、OLED屏幕显示

为了节省引脚资源,选择与30102共用I2C接口:

U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* clock=*/SCL, /* data=*/SDA, /* reset=*/U8X8_PIN_NONE);  // OLEDs without Reset of the Display


有些显示不需要每次都刷新,可以在循环外面先打印到OLED屏幕上。

  u8x8.setFont(u8x8_font_chroma48medium8_r);
  u8x8.drawString(0, 1, "Hello World!");
  u8x8.setInverseFont(1);
  u8x8.drawString(0, 0, "   Mouser EEtree");
  u8x8.setInverseFont(0);


  u8x8.drawString(0, 3, "BPM:");

在第三步中获取到心率数据后,先打印到一个字符串,然后再打印到屏幕上。

sprintf(buffer, "%d", beatAvg);
u8x8.drawString(5, 3,buffer);


遇到的问题:心率测量不准确

原因是循环中打印OLED相关代码耗时,影响了心率计算的准确性。

解决办法:把这部分代码注释掉,然后再初始化的时候把不用动态刷新的数据提前打印好。

#if 0
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  u8x8.drawString(0, 1, "Hello World!");
  u8x8.setInverseFont(1);
  u8x8.drawString(0, 0, "   Mouser EEtree");
  u8x8.setInverseFont(0);
  u8x8.drawString(0, 3, "BPM:");



    u8x8.setCursor(64, 32);
  u8x8.print(beatAvg);
#endif  


五、蓝牙传输

需要提前安装Arduino BLE库,并在源文件中包含如下头文件:

#include <ArduinoBLE.h>

接着定义BLE Service与Characteristic:

BLEService HeartBeatService("19B10010-E8F2-537E-4F6C-D104768A1214");  // create service
BLEByteCharacteristic ledCharacteristic("19B10011-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);

设置设备广播名称并开始广播:

  // set the local name peripheral advertises
  BLE.setLocalName("Mouser EEtree");
  // set the UUID for the service this peripheral advertises:
  BLE.setAdvertisedService(HeartBeatService);

  // add the characteristics to the service
  HeartBeatService.addCharacteristic(ledCharacteristic);

  // add the service
  BLE.addService(HeartBeatService);
  ledCharacteristic.writeValue(0);

  // start advertising
  BLE.advertise();
  Serial.println("Bluetooth® device active, waiting for connections...");


在主循环中轮询BLE相关的事件:

  // poll for Bluetooth® Low Energy events
  BLE.poll();


根据红外传感器数据来判断手指是否放下,手指正常方向后,才传输计算出来的心率平均值,否则传输0.

  if (irValue < 50000){
    sprintf(buffer, "%s", "NO Finger");
    ledCharacteristic.writeValue(0);
  }
  else
    ledCharacteristic.writeValue(beatAvg);


实物展示:

image.png

点击Connect,连接BLE助手与开发板:

image.png


使能Notify功能,这样设备端数据发生变化后,BLE助手可以及时看到。

image.png


image.png

总结

这次借助贸泽电子与硬禾学堂的M-Design平台,设计并实现了简易心率计系统。可以方便快速的测量心率。

在评委的建议下,增加了蓝牙传输等功能,并重新设计了PCB底板,提高了系统的稳定性。


最后,感谢贸泽电子和硬禾学堂,学到了PCB设计,蓝牙传输和心率检测等方面的知识。


附件下载
M_Design_mx30102_sensortest_oled_ino_v3.ino
Gerber_arduino-nano-matter-mx30102-oled_arduino-nano-matter-pcb_2025-02-09.zip
团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号