拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于Wirelings与PPG传感器的心率监测系统:硬件连接、算法实现与OLED显示

1. 项目缘起当心率传感器遇上微型显示屏最近在捣鼓一些可穿戴设备原型核心需求是想实时、直观地看到自己的心率数据。市面上成品的心率手环当然方便但作为一个喜欢折腾的硬件爱好者我更想自己动手从传感器读数开始一步步把数据“可视化”出来。这个过程中我选择了两个核心组件一个模拟输出的Pulse Sensor脉搏传感器和一块小巧的0.96英寸OLED显示屏。为了让连接更简洁避免面包板上飞线如麻的窘境我决定采用TinyCircuits的Wirelings生态系统。这个项目就是关于如何将它们三者有机结合打造一个便携、直观的心率监测显示模块。Pulse Sensor是一款非常经典的光电容积脉搏波PPG传感器它通过LED照射皮肤并用光电探测器接收反射光由于血液流动会吸收部分光线因此反射光的强度会随心跳周期性地微弱变化。传感器将这个模拟信号输出我们通过微控制器的ADC模数转换器引脚读取再经过一定的算法处理就能提取出心率值。而OLED显示屏以其自发光、高对比度、低功耗的特性成为便携设备显示的绝佳选择尤其适合在环境光下清晰展示数字和波形。那么为什么选择Wirelings传统上我们将传感器和显示屏连接到Arduino或其他主控板时需要连接多条线电源、地、数据线不仅容易接错而且整个结构显得臃肿不适合嵌入到紧凑的原型中。Wirelings系统提供了一种微型化的连接方案每个模块传感器、显示屏等都集成在一个微小的PCB上并通过一个统一的、类似“菊链”的4针接口包含I2C、电源和地进行连接。你只需要一个Wirelings适配器板将其插在主控板如Arduino Zero、TinyDuino上然后其他Wirelings模块就可以像堆叠乐高积木一样通过微型连接线串联起来极大地简化了布线和物理结构。这个项目的目标很明确搭建一个硬件上简洁可靠、软件上能稳定计算并显示心率BPM和脉搏波形的系统。它不仅是学习模拟信号处理、I2C通信和嵌入式显示的好案例其成果本身也可以作为后续健康监测项目的数据采集前端或者一个独立的桌面心率监测小工具。接下来我将从硬件选型与连接、核心算法解析、代码实现与优化以及实际搭建与调试心得四个方面详细拆解整个过程。2. 硬件选型、连接与电路解析硬件是整个项目的基础选择正确的组件并理解其连接方式是成功的第一步。这里的关键在于确保信号通路清晰、电源稳定并且物理结构紧凑。2.1 核心组件深度剖析首先我们来看看三位“主角”Pulse Sensor (Wirelings版本)这不是普通的Pulse Sensor而是TinyCircuits为其Wirelings生态系统定制的版本。它核心的PPG传感器部分与常见的模拟Pulse Sensor相同但输出接口被集成到了Wirelings的4针接口中。关键点在于它输出的仍然是模拟信号。在Wirelings接口定义中这通常意味着它使用了4针中的某一根线作为模拟信号输出例如标为“A”的引脚而不是数字I2C信号。购买或使用时务必确认其引脚定义。OLED Display (Wirelings版本)我选用的是0.96英寸、128x64分辨率的SSD1306驱动芯片的OLED屏并且是Wirelings接口的。SSD1306通常通过I2C或SPI通信Wirelings版本几乎无一例外采用I2C因为Wirelings的4针接口天然包含I2C的SDA和SCL线。I2C通信节省引脚软件库成熟是首选。Wirelings Adapter (适配器板)这是连接主控板与Wirelings模块的桥梁。它本身也是一个Wirelings模块一端是Wirelings接口另一端是扩展排针可以插到主控板如Arduino Uno, Zero, TinyDuino的对应引脚上。适配器板会将Wirelings接口的4条线VCC, GND, SDA, SCL映射到主控板的特定引脚。这里有一个至关重要的细节你需要根据你所用的主控板型号选择正确的适配器板或者手动确认引脚映射关系。例如TinyCircuits的适配器板可能默认将SDA/SCL映射到Arduino的A4/A5Uno的I2C引脚但如果你用的是ESP32开发板其I2C引脚可能不同就需要使用通用适配器并自行接线。2.2 连接拓扑与电源考量连接方式非常直观得益于Wirelings的“菊链”设计主控板-Wirelings适配器板-Pulse Sensor Wirelings-OLED Display Wirelings。所有模块通过细小的4针线缆顺序连接。从适配器板出来的VCC和GND为整个链路上的模块供电SDA和SCL则为I2C设备这里是OLED屏提供通信总线。Pulse Sensor作为模拟设备它只使用VCC、GND和其专用的模拟信号线假设是“A”线。电源稳定性警告OLED屏在点亮像素尤其是显示全白画面时瞬时电流可能较大。而Pulse Sensor内部的LED也需要驱动。如果使用USB供电一般问题不大。但如果使用电池供电特别是小型锂聚合物电池需要注意电压跌落。建议在VCC和GND之间并联一个100μF或更大的电解电容作为电源轨的“蓄水池”平滑可能出现的电流峰值防止系统复位或传感器读数异常。2.3 主控板的选择与引脚映射虽然Wirelings简化了模块间连接但主控板的选择决定了你的编程环境和性能上限。对于这个项目推荐以下几类Arduino Zero/M0系列这是与TinyCircuits生态系统兼容性非常好的选择。它们基于ARM Cortex-M0内核具有足够的处理能力进行实时的心率算法计算并且通常具有多个ADC通道。使用官方Wirelings适配器时引脚映射是预设好的最省心。ESP32系列如果你希望未来增加蓝牙/Wi-Fi功能将心率数据无线发送到手机或服务器ESP32是强力候选。你需要一个通用的Wirelings适配器板然后手动将它的VCC、GND、SDA、SCL连接到ESP32开发板的对应引脚。例如ESP32的默认I2C引脚是GPIO21 (SDA)和GPIO22 (SCL)。Pulse Sensor的模拟输出线则需要连接到一个ADC引脚如GPIO34。TinyDuino这是TinyCircuits自家的超小型主控板与Wirelings是天作之合可以构建极其紧凑的系统。但需要注意其处理器性能和IO数量。关键操作确认模拟引脚编号。在代码中你需要指定读取Pulse Sensor的模拟引脚。这个引脚号不是Wirelings接口上的“A”线编号而是该“A”线最终被适配器板映射到主控板上的那个具体ADC引脚编号。例如使用Arduino Uno和特定适配器时“A”线可能被映射到模拟引脚A0。你必须查阅适配器板或主控板的文档来确定这个数字。这是硬件连接中最容易出错的一步。3. 心率算法核心从噪声中捕捉心跳硬件搭建好后最核心的挑战在于软件如何从那个充满噪声的、微弱的模拟信号中准确计算出心率Pulse Sensor输出的原始信号可以大致分为三部分直流分量由组织、骨骼等非脉动部分反射形成、交流分量由动脉血液脉动形成即我们需要的信号以及各种高频噪声环境光变化、运动伪影、电路噪声等。3.1 信号处理流程拆解一个稳健的心率检测算法通常包含以下步骤我将在代码实现部分给出具体示例采样与缓存以稳定的频率如100Hz至500Hz读取ADC值。需要一个数组环形缓冲区来存储最近一两秒的原始采样数据用于后续处理。直流分量去除与放大这是关键一步。首先计算缓存数据一段时间如1秒的平均值这个平均值近似代表直流分量。然后将每个新的采样值减去这个动态更新的平均值。这样我们就得到了一个围绕零点波动的交流信号。接着可以对这个信号乘以一个系数进行放大使其幅值更易于观察和处理。// 伪代码示例 long runningTotal 0; int sampleBuffer[BUFFER_SIZE]; // 环形缓冲区 int bufferIndex 0; // 每次采样后 int rawValue analogRead(PULSE_PIN); runningTotal - sampleBuffer[bufferIndex]; // 减去最旧的值 runningTotal rawValue; // 加上最新的值 sampleBuffer[bufferIndex] rawValue; bufferIndex (bufferIndex 1) % BUFFER_SIZE; int average runningTotal / BUFFER_SIZE; // 动态平均值 int normalized rawValue - average; // 去除直流 int amplified normalized * 10; // 放大系数需根据实测调整低通滤波为了抑制高频噪声特别是50/60Hz的工频干扰需要对放大后的信号进行低通滤波。一个简单有效的方法是使用一阶无限脉冲响应IIR低通滤波器它计算简单适合嵌入式系统。// 一阶IIR低通滤波器 float alpha 0.1; // 平滑系数越小滤波越强但延迟越大 static float filteredValue 0; filteredValue (alpha * amplified) ((1 - alpha) * filteredValue);选择合适的alpha值需要在平滑度和响应速度之间权衡。对于心率信号通常小于5Hz一个0.05到0.2的alpha值通常比较合适。峰值检测在滤波后的波形上检测波峰即每次心跳对应的点。算法需要避免噪声引起的误触发。常见的策略包括阈值法设定一个动态阈值。只有当信号值超过该阈值并且是一个局部最大值比前后若干点都高时才被认为是一个候选峰值。阈值更新阈值不能是固定的。它可以基于最近一段时间信号的最大值和最小值进行动态调整例如threshold (max min) * 0.6。不应期在一次有效心跳被检测后设置一个“不应期”例如200-300毫秒在此期间内忽略任何新的峰值因为生理上不可能在如此短的时间内发生两次心跳。心率计算记录下连续两个有效峰值之间的时间间隔Inter-Beat Interval, IBI单位是毫秒。心率BPM的计算公式为BPM 60000 / IBI。为了显示稳定通常不会每次心跳都更新显示值而是计算最近几次如5-10次IBI的平均值再用其计算平均BPM。3.2 应对噪声与运动伪影的实战技巧在实际佩戴尤其是轻微运动时信号质量会急剧下降。除了上述的滤波算法还有一些软硬件技巧硬件固定确保传感器紧密、舒适地贴合皮肤通常指腹或耳垂效果较好避免环境光直射。可以使用不透光的胶带或定制一个遮光罩。软件容错在算法中加入“信号质量”评估。例如计算最近一段时间波形的幅度最大值-最小值。如果幅度太小说明信号太弱如果IBI变化过于剧烈如从500ms突然跳到200ms则很可能是误检测应丢弃该次结果。状态指示在OLED屏上除了显示BPM数字最好增加一个简单的信号质量指示比如一个跳动的小点或者“Signal OK”/“No Signal”的文字提示让用户知道当前读数是否可靠。4. 代码实现驱动OLED与整合算法有了算法思路我们需要用代码将其实现并驱动OLED屏进行显示。这里以使用Arduino IDE和常见的库为例。4.1 库的安装与初始化首先需要安装两个核心库Adafruit SSD1306和Adafruit GFX这是驱动SSD1306 OLED屏最广泛使用的库。可以通过Arduino库管理器搜索安装。WireArduino内置的I2C通信库。初始化代码如下#include Wire.h #include Adafruit_GFX.h #include Adafruit_SSD1306.h #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 // 如果屏幕有复位引脚则定义否则用-1 #define I2C_ADDRESS 0x3C // OLED的I2C地址常见为0x3C或0x3D Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, Wire, OLED_RESET); // Pulse Sensor 引脚定义 const int pulsePin A0; // 请根据你的实际连接修改 void setup() { Serial.begin(115200); // 初始化OLED if(!display.begin(SSD1306_SWITCHCAPVCC, I2C_ADDRESS)) { Serial.println(F(SSD1306 allocation failed)); for(;;); // 卡住 } display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.println(Pulse); display.println(Monitor); display.display(); delay(2000); display.clearDisplay(); // 初始化用于心率算法的变量... }4.2 主循环逻辑与显示更新主循环loop()函数需要高效地完成四件事采样、处理、计算心率、更新显示。为了避免显示刷新相对较慢影响采样定时最好将采样放在一个由定时器或精确delay控制的独立节奏中。这里用一个简单但有效的方法// 全局变量 unsigned long lastSampleTime 0; const int SAMPLE_INTERVAL_MS 10; // 100Hz采样率 int bpm 0; bool pulseDetected false; int rawSignal 0; int filteredSignal 0; void loop() { unsigned long currentTime millis(); // 1. 定时采样 if (currentTime - lastSampleTime SAMPLE_INTERVAL_MS) { lastSampleTime currentTime; rawSignal analogRead(pulsePin); // 2. 调用信号处理函数包含去直流、滤波 processSignal(rawSignal); // 3. 调用峰值检测函数更新BPM detectPeak(filteredSignal); } // 4. 定期刷新显示例如每秒2-4次避免闪烁 static unsigned long lastDisplayUpdate 0; if (currentTime - lastDisplayUpdate 250) { // 每250ms更新一次显示 lastDisplayUpdate currentTime; updateDisplay(); } }4.3 显示界面设计在updateDisplay()函数中我们可以充分利用128x64的像素空间区域1大字BPM在屏幕中央用大字体如setTextSize(4)显示计算出的心率值例如“72”。区域2波形图在屏幕下方绘制一个实时滚动的脉搏波形图。这需要维护一个用于绘图的数值数组。每次刷新时将整个图形向左平移一个像素然后在最右侧绘制最新的filteredSignal值对应的点。// 波形图绘制示例简化 #define GRAPH_WIDTH 128 #define GRAPH_HEIGHT 30 #define GRAPH_Y_OFFSET 34 int graphData[GRAPH_WIDTH]; // 在updateDisplay()中 // 1. 平移旧数据 for (int i0; iGRAPH_WIDTH-1; i) { graphData[i] graphData[i1]; } // 2. 存入新数据将滤波后的信号值映射到绘图高度范围内 graphData[GRAPH_WIDTH-1] map(constrain(filteredSignal, -512, 512), -512, 512, 0, GRAPH_HEIGHT); // 3. 清空图形区域并重绘 display.fillRect(0, GRAPH_Y_OFFSET, GRAPH_WIDTH, GRAPH_HEIGHT, SSD1306_BLACK); for (int i0; iGRAPH_WIDTH; i) { display.drawPixel(i, GRAPH_Y_OFFSET GRAPH_HEIGHT - graphData[i], SSD1306_WHITE); }区域3状态指示在屏幕顶部角落可以画一个小心脏图标当检测到一次心跳时让它短暂变大或改变颜色提供视觉反馈。或者用文字显示“稳定”、“检测中”等状态。5. 系统集成、调试与性能优化将所有部分组合起来后真正的挑战才开始让系统稳定可靠地工作。5.1 上电调试与信号观察首先不要急于求成显示心率。将rawSignal和filteredSignal通过Serial.println()打印到串口绘图器Arduino IDE自带工具。这是最重要的调试手段。将手指稳定地放在传感器上观察串口绘图器中的波形。你应该能看到一个清晰的、周期性的波动。如果波形是一条直线或噪声极大检查硬件连接特别是地线是否共地、电源电压、以及传感器是否贴紧。调整算法中的参数放大倍数、低通滤波的alpha值、峰值检测的阈值和不应期。目标是让滤波后的波形平滑峰值突出且每次心跳只产生一个峰值。5.2 优化计算效率与内存使用嵌入式资源有限优化很重要避免浮点数运算像alpha * amplified这样的浮点运算在8位AVR单片机如Uno上很慢。可以改用整数运算。例如将alpha设为0.1等价于filteredValue (amplified 9 * filteredValue) / 10。使用更快的ADC如果主控板支持如Arduino Zero的ARM芯片可以提高ADC采样率并可能启用硬件均值功能来降低噪声。优化显示刷新display.display()是全屏刷新比较耗时。如果只更新了部分区域如只改了BPM数字可以考虑使用display.drawBitmap()局部更新但这更复杂。一个折中方案是降低显示刷新率如每秒4次并确保在显示刷新期间不进行复杂的计算或采样。5.3 提升心率检测的鲁棒性基础算法在静坐时可能工作良好但实用中需要更强健动态阈值算法升级实现一个更聪明的阈值。例如跟踪信号的最大值和最小值但让它们缓慢衰减。当检测到峰值时阈值可以设置为(max min) / 2或min (max - min) * 0.6。最大值和最小值每隔一段时间如每秒衰减1%以适应信号幅度的缓慢变化。IBI有效性检查计算出的IBI应该在生理合理范围内例如对应心率40-200 BPM。如果超出范围则忽略这次心跳检测。同时检查连续几次IBI的方差如果变化过大可以认为信号不稳定显示上一次的有效平均值或“---”。均值与平滑显示计算BPM时不要只使用最后一次IBI。维护一个包含最近5-10次IBI的环形缓冲区计算其平均值后再转换为BPM。对于显示给用户的BPM数值还可以进行一次滑动平均滤波避免数字跳动过快。5.4 物理封装与电源管理为了让项目更像一个产品原型可以考虑3D打印外壳设计一个可以固定主控板、电池并让传感器和OLED屏幕露出来的小盒子。这对于指戴式传感器尤其有用。低功耗优化如果使用电池考虑在代码中实现休眠。当一段时间没有检测到心率信号时让微控制器进入低功耗休眠模式OLED屏幕关闭。通过触摸传感器或按钮唤醒。这需要更复杂的编程但能极大延长续航。经过以上步骤你就能获得一个由自己亲手搭建、代码一行行写出来的心率监测显示模块。它不仅仅是一个显示数字的工具更是一个融合了模拟电路知识、数字信号处理算法和嵌入式系统开发的综合实践项目。当你看到OLED屏上随着自己心跳而规律跳动的波形和数字时那种成就感是无可替代的。这个项目也为后续更复杂的生物信号监测如血氧饱和度打下了坚实的基础。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门