非常感谢贸泽和硬禾举办的本次M-Design设计竞赛活动,本次选择了推荐的方向二:安全监测中的“医疗检测”。
具体完成的内容是
- 基于主控Arduino Nano Matter开发板采集心率传感器MX30102数据
- 基于u8g2图形库和硬件IIC接口驱动OLED屏幕,用于动态显示手指是否放下以及显示测量结果
- 基于Arduino BLE协议栈,在采集到心率数据后,通过BLE传输至上位机进行心率数据的查看。
系统运行框图如下:
首先展示实物图:
从上之下分别是MX30102,OLED与ARDUINO NANO MATTER开发板。
后来重新设计了PCB扩展底板,从而避免了复杂的接线,并提高了小系统的稳定性。附件是立创EDA标准版的GEBER文件。
如下是简易扩展板的原理图:
如下是PCB:
3D预览:
实物组装:
一、项目框图
主控模块是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屏幕。
心率检测模块:
MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。它集成了一个红光LED和一个红外光LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路。MAX30102采用一个1.8V电源和一个独立的5.0V用于内部LED的电源,应用于可穿戴设备进行心率和血氧采集检测,佩戴于手指点耳垂和手腕处。标准的I2C兼容的通信接口可以将采集到的数值传输Arduino、KL25Z、STM32、STC51等单片机进行心率和血氧计算。
OLED屏幕:
具有GND, VCC, SCL, SDA四根引脚,I2C通信接口。注意7位地址是0X3C.
二、程序流程图
程序整体运行流程如下所示:
三、心率检测
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);
实物展示:
点击Connect,连接BLE助手与开发板:
使能Notify功能,这样设备端数据发生变化后,BLE助手可以及时看到。
总结
这次借助贸泽电子与硬禾学堂的M-Design平台,设计并实现了简易心率计系统。可以方便快速的测量心率。
在评委的建议下,增加了蓝牙传输等功能,并重新设计了PCB底板,提高了系统的稳定性。
最后,感谢贸泽电子和硬禾学堂,学到了PCB设计,蓝牙传输和心率检测等方面的知识。