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

ESP32+MAX30102制作心率检测仪:从原理到代码全解析

“听心跳”这事听起来像医院里的心电图检查但你要是自己动手做一次就会发现其实并不复杂。ESP32这块几十块钱的开发板加上一个指甲盖大小的MAX30102传感器就能实时把心率数字算出来。这篇文章就是给零基础的人准备的不需要嵌入式经验不需要焊接只要会插线、会复制代码就能跑起来。我会从原理讲起把硬件接线、开发环境、代码逐行拆开最后把我在实际调试中遇到的那些坑也一并说清楚。跟着走完一遍你不仅能拥有一台个人心率检测装置更重要的是你会真正搞懂“传感器数据是怎么变成人看懂的数值”这一整条链路。1. 先把这个项目拆明白ESP32加一个传感器到底是怎么“听”到心跳的1.1 这个项目要解决什么问题先说结论我们要做的东西是把MAX30102这颗传感器贴在手指上通过它内部的红光和红外光照射皮肤检测血液流动引起的细微光变化再把变化量交给ESP32处理最终在电脑串口上输出一个实时跳动的心率数值BPM每分钟心跳次数。整个过程听起来像“玄学”但拆开看就是一套完整的数据链路发光 → 反射 → 光电转换 → 模数转换 → 数值计算 → 显示输出。MAX30102负责前四步ESP32负责后两步。这也是我第一次接触嵌入式传感器项目时最震撼的一点原来一个“测量心跳”的功能底层就是光、电、算法三件事的组合。选ESP32做主控原因很直接它便宜、好买、资料多。ESP32-WROOM-32开发板在电商平台二三十块就能买到双核240MHz的处理器对心率检测这种任务来说绰绰有余而且自带WiFi和蓝牙意味着以后想做个手机App把心率显示出来或者把数据传到云端都不用换主控。相比之下如果用Arduino Uno性能和无线能力就差了一截后续扩展会受限。MAX30102这颗传感器同样是性价比之选。它把红光LED、红外LED、光电检测器和模数转换器集成在了一个3mm见方的小芯片里外部只需要很少的电路就能工作。更关键的是它走的是I2C接口一共只需要两根信号线和ESP32连接非常方便。1.2 心率检测的核心原理PPG光电容积脉搏波可能有人会问光怎么能测心率这里就要说到一个专业名词——PPG光电容积脉搏波描记法。简单类比一下你用强光手电筒照手指在另一侧看到的光是透过去的但MAX30102是反射式的LED和光检测器在同一边光照到皮肤后会往四周反射一部分被光电二极管接收。问题在于皮肤、肌肉、骨骼对光的反射是相对固定的唯独血液是流动的。心脏收缩时动脉血流量增加指端毛细血管里的血液量变多血红蛋白吸收的红光和红外光也随之增加这就导致反射回去的光强减弱心脏舒张时血流量减少反射光又变强。这个周期性的光强变化频率正好等于心率。MAX30102内部的光电二极管把这种微弱的光强变化转成电流信号再通过内部的ADC模数转换器变成数字量。这个数字量就是代码里read到的IR值。你会在串口绘图器上看到一条像波浪线一样的曲线每个波峰对应一次心跳。程序的任务就是数清楚这条波浪线在一分钟里有多少个波峰这就是BPM。为什么不直接用红光而要用红外因为红光更容易被含氧血红蛋白吸收在血氧饱和度测量时有优势而红外光穿透力更强受运动伪影和肤色差异影响相对小心率检测场景下信号更稳定。MAX30102的惯例做法是同时使用红光和红外通道心率算法默认用红外通道IR数据。2. 硬件准备与接线零基础也能看懂的连接方案2.1 材料清单与选型建议做这个项目需要的物料很少清单如下ESP32开发板一块推荐标准WROOM-32核心板带USB转串口芯片的那种型号无所谓关键是要支持Arduino IDE烧录MAX30102传感器模块一个市面上常见的是红色PCB小板引脚有VIN、GND、SCL、SDA、INT有的还带PA、PG引脚杜邦线若干母对母用来把传感器连到开发板面包板一个可选方便固定和扩展USB数据线一根这里特别提醒很多人的数据线只能充电不能传数据这种线是烧不进程序的必须确认线芯里有数据传输线选MAX30102模块时注意看板子上的丝印尽量选引脚标记清晰的。有些劣质模块的LED和光电二极管上面会贴一层保护膜使用前一定要撕掉不然信号强度会大打折扣。还有尽量买模块上带LDO稳压芯片的版本因为MAX30102本身供电范围是1.8V到3.3V如果板子自带稳压和电平转换那ESP32开发板上的3.3V直接供电就很稳。2.2 接线图与I2C引脚选择连接方式极其简单核心就是供电和两根I2C数据线VIN → 开发板3V3GND → 开发板GNDSDA → 开发板GPIO21SCL → 开发板GPIO22稍微解释一下为什么是GPIO21和GPIO22。ESP32有多组引脚可以复用为I2C功能但Arduino环境下默认的Wire接口就是GPIO21作为SDA、GPIO22作为SCL。这组引脚在大多数ESP32开发板上被引出到排针位置好找而且它们内部有上拉电阻配合MAX30102模块使用稳定性不错。如果你想换成其他引脚代码里可以调用Wire.begin(SDA, SCL)指定但新手阶段不建议折腾按默认来最省事。需要说明的是MAX30102正常工作电压是1.8V至3.3V而ESP32的GPIO输出也是3.3V两者电平匹配不用额外加电平转换模块。这里有一个容易踩的坑如果你用的是Arduino Uno这类5V主控板直接接MAX30102会超压轻则读数异常重则烧芯片。本项目中ESP32是3.3V的逻辑电平正好合适。2.3 上电前的检查清单我见过太多人通电后发现读数不对结果最后查出来是接线问题。所以在插线之前先按这个清单做一遍确认开发板USB口附近没有金属碎屑或焊锡粘连特别是刚拆开的新板子用万用表电阻档量一下模块的VIN和GND之间是否短路正常时电阻值应该很大接近无穷大杜邦线插接要稳固优先选那种带防脱卡扣的母座线普通杜邦线插上去后用手轻轻拉一下看有没有松动所有接线都完成后再上电不要热插拔传感器到已经通电的板子上。我第一次做这个项目时就是没注意杜邦线松动的问题结果I2C怎么扫描都扫不到设备折腾了半天才发现是GND线接触不良。这类问题靠万用表和认真检查就能避免大半。3. 开发环境搭建从零装好Arduino IDE并让ESP32跑起来3.1 为什么新手首选Arduino IDEESP32官方支持两种主流的开发环境ESP-IDF乐鑫官方的框架功能全但门槛高和Arduino IDE门槛低库生态丰富。对于零基础入门我强烈建议先选Arduino IDE。原因很简单你需要的大部分功能都已经有人封装成库了你要做的只是调用API而不是去啃寄存器手册。以本项目为例MAX30102的心率检测算法平时自己做要写几百行代码去处理滤波、阈值、峰值检测但SparkFun库把这些都封装好了调用一个checkForBeat()就能判断是否检测到一次心跳。这在ESP-IDF环境下要复杂得多。Arduino IDE的安装过程不赘述去官网下对应系统版本一路下一步即可。重点说一下ESP32开发板包的添加这是很多人卡住的第一个地方。3.2 给Arduino IDE添加ESP32开发板支持打开Arduino IDE后在“文件 → 首选项 → 附加开发板管理器网址”里填入乐鑫官方的JSON地址https://espressif.github.io/arduino-esp32/package_esp32_index.json然后进入“工具 → 开发板 → 开发板管理器”搜索“esp32”找到“esp32 by Espressif Systems”点击安装。整个包很大包含多个版本的工具链需要下载数十分钟期间要保持网络畅通。这里有个实用经验如果你网络不好安装过程容易超时失败报错信息通常带有“net/http: request canceled”字样。这种情况下建议找一个可用的加速镜像地址或者直接下载离线压缩包手动安装。手动安装的步骤是把离线包解压后放到Arduino IDE的hardware/espressif目录下重启IDE即可识别。用离线包的好处是不依赖外部下载装完就能编译。安装完成后在“工具 → 开发板”菜单里应该能看到大量ESP32型号选项。本项目选择“ESP32 Dev Module”即可这对应市面上绝大多数的ESP32-WROOM-32开发板。如果你的板子是ESP32-S3或者ESP32-C3板型选择要对应调整但这个项目的接线和代码逻辑不变只是引脚映射可能不同。3.3 安装MAX30102的核心驱动库接下来要装两个库一个是SparkFun MAX3010x Pulse and Proximity Sensor Library负责传感器驱动另一个是SparkFun Heart Rate Monitor Library负责心率算法。后者其实是一个名为SparkFun_Heart_Rate_Monitor_Library的库里面包含heartRate.h文件。在Arduino IDE里打开“工具 → 管理库”搜索“MAX30105”第一个结果通常就是SparkFun的库点安装。然后搜索“SparkFun Heart Rate”安装第二个库。这两个库会自动处理MAX30102的寄存器初始化和数据读取你不需要关心底层寄存器地址。这里想多说一句既然装了这两个库代码里#include MAX30105.h和#include heartRate.h的引入关系就固定了。MAX30105和MAX30102是同一家族的传感器寄存器和驱动兼容SparkFun的库也同时支持两者。如果以后你手头只有MAX30102代码完全不用改。4. 完整代码实现每一行的作用都给你讲透4.1 初始化让传感器开始工作下面这段代码就是项目的核心我会逐段解释。打开Arduino IDE新建一个Sketch把下面的代码粘贴进去#include Wire.h #include MAX30105.h #include heartRate.h MAX30105 particleSensor; const byte RATE_SIZE 4; byte rates[RATE_SIZE]; byte rateSpot 0; long lastBeat 0; float beatsPerMinute; int beatAvg;第一部分是头文件和全局变量。MAX30105 particleSensor创建了传感器对象后面所有操作都通过这个对象完成。RATE_SIZE是平均窗口大小用来做心率平滑——我们不直接用瞬时BPM而是取最近4次心跳间隔的平均值这样可以减少单次测量误差带来的跳动。lastBeat记录上一次检测到心跳的时刻单位是毫秒。beatsPerMinute存放当前瞬时心率beatAvg存放平滑后的平均心率。这几个变量在loop里会被不断更新。4.2 setup函数初始化I2C和传感器参数void setup() { Serial.begin(115200); Serial.println(MAX30102 Heart Rate - ESP32); if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) { Serial.println(MAX30102 was not found. Please check wiring/power.); while (1); } particleSensor.setup(); particleSensor.setPulseAmplitudeRed(0x0A); particleSensor.setPulseAmplitudeGreen(0); }Serial.begin(115200)初始化串口波特率设为115200。这里建议和串口监视器右下角的波特率选项保持一致否则会出现乱码。particleSensor.begin(Wire, I2C_SPEED_FAST)执行的是传感器初始化它会检查I2C总线上能不能找到地址为0x57的MAX30102设备。如果找不到会输出提示信息并让程序停住。setPulseAmplitudeRed(0x0A)设置了红光LED的驱动电流等级。0x0A在数值上对应一个较小的电流值因为心率检测只需要微弱的光太亮反而容易导致信号饱和。setPulseAmplitudeGreen(0)把绿光LED的电流设为0MAX30102虽然设计了多路LED驱动但这个模块硬件上一般只接红光和红外绿光通道用不上直接关掉省电。4.3 loop函数读取数据并计算心率void loop() { long irValue particleSensor.getIR(); if (checkForBeat(irValue) true) { long delta millis() - lastBeat; lastBeat millis(); beatsPerMinute 60 / (delta / 1000.0); if (beatsPerMinute 60 beatsPerMinute 200) { rates[rateSpot] (byte)beatsPerMinute; rateSpot % RATE_SIZE; beatAvg 0; for (byte x 0; x RATE_SIZE; x) beatAvg rates[x]; beatAvg / RATE_SIZE; } } Serial.print(IR); Serial.print(irValue); Serial.print(, BPM); Serial.print(beatsPerMinute); Serial.print(, Avg BPM); Serial.print(beatAvg); if (irValue 50000) Serial.print( No finger?); Serial.println(); }getIR()返回红外通道的当前ADC值这是算法的原始输入。checkForBeat()来自heartRate库它内部实现了基于IR信号的脉冲检测逻辑当检测到一个波形峰值时返回true。每次返回true说明心脏跳了一下。计算BPM的逻辑很直白用当前毫秒时间减去上一次心跳的毫秒时间得到相邻两次心跳的间隔delta单位是毫秒。一分钟是60000毫秒所以60 / (delta / 1000.0)换算成每分钟次数。这里有个边界判断只有BPM在60到200之间才纳入平均窗口因为这个范围基本覆盖了正常人体心率超出范围的数据大概率是噪声或者手指没放好。那irValue 50000这个阈值怎么来的这是经验值。手指没有贴合传感器时红外反射光很弱ADC读数通常只有几千手指轻轻贴上去后读数会飙升到几万甚至十几万。把阈值设为50000是为了区分“传感器上有没有手指”。不同环境光和不同肤色会有差异如果你发现这个判断不准可以自己在串口监视器里观察没有手指时的IR值然后调整阈值。4.4 串口输出该如何看代码最后通过Serial.print输出了三组关键数据IR原始值、瞬时BPM、平均BPM以及一个“手指是否放上”的状态提示。打开串口监视器你会看到每秒刷新好几行数据就像这样IR132456, BPM75, Avg BPM74 IR132890, BPM75, Avg BPM74 IR131230, BPM72, Avg BPM73重点看Avg BPM这个值。第一次跑通代码时你可能发现BPM在300甚至1000多乱跳——这很正常因为峰值检测算法需要一段数据来适应波形。放稳手指保持静止等待5到10秒数值就会回落到正常范围。我用实际数据验证过安静坐着稳定读数基本维持在70到80之间。5. 烧录、运行与数据验证从代码到真实心率5.1 编译烧录的完整流程代码写好后做这几步操作先选择开发板“ESP32 Dev Module”再选择正确的COM端口。端口选择在“工具 → 端口”里正常情况下会显示一个带芯片型号标识的串口设备。如果这里空白先检查数据线是不是只能充电那种再检查是否安装CP210x或CH340驱动。Windows系统下CP2102芯片的设备管理器里会显示为“Silicon Labs CP210x USB to UART Bridge”CH340则显示为“USB-SERIAL CH340”。驱动没装好IDE是找不到端口的。点击“上传”按钮后Arduino IDE会先编译再烧录。ESP32第一次烧录时IDE会自动下载额外的工具链耗时可能比较久这也是很多新手在“编译”阶段就卡住的原因。编译输出的日志底部如果出现“Connecting…____”卡住不动大概率是板子没有自动进入下载模式。老版本固件需要手动按住板上的BOOT键出现“Connecting”的同时松开BOOT就能正常烧录。新版本ESP32使用USB转串口芯片的DTR/RTS信号自动控制下载模式一般不需要手动操作。烧录完成后打开串口监视器波特率设为115200等待板子复位。此时如果手指还没放上去会看到IR值很小“No finger”提示频繁出现。把手指轻轻按在MAX30102的传感器窗口上保持不动下面的数据就会变成有节奏的波动。5.2 手指放置技巧与波形观察这里必须专门讲一下手指应该怎么放用食指或中指的指腹轻轻覆盖住MAX30102模块上的传感器开窗区域让手指的肉完全盖住LED和光电二极管。注意两点一是不要用力按压压得太紧会把毛细血管里的血挤走反而测不到信号二是手指和传感器之间的接触要稳定轻微的移动都会导致波动波形变得毛糙。更直观的办法是使用Arduino IDE自带的“串口绘图器”它在工具菜单下打开后会自动把串口输出的数字绘制成曲线。把IR后面的数字作为绘图对象你会在绘图器上看到一条类似正弦波的曲线每个周期对应一次心跳。当手指放稳时波形非常规律手指晃动时波形会剧烈混乱。这就是最有成就感的时刻——你亲眼看到了自己的脉搏波形。5.3 数据验证用手摸脉搏对照一下拿到读数后建议先和真实心率对照一下。最原始的方法就是摸自己的颈动脉或手腕桡动脉数15秒脉搏乘以4得到一分钟心率再和串口输出的Avg BPM对比。如果两者误差在5以内传感器和算法就是准的误差偏大多半是滤波和阈值参数需要微调。另外有个经验MAX30102测量时环境光影响很大。在强光直射下外部光可能淹没LED反射光的光强变化导致波形失真。我试过在台灯底下测数据就明显比在暗处测要跳得厉害。如果你发现波形不稳先把环境光调暗或者用手指更严实地遮挡传感器窗口再观察数据。6. 新手最容易踩的坑问题排查与解决实录6.1 初始化失败“MAX30102 was not found”怎么办这个提示说明I2C总线上找不到设备。排查顺序按频率从高到低排检查接线是否松动尤其是GND。I2C是两线协议但电源和地是基础缺了地线IC根本没法工作。确认VIN接的是3V3而不是某个其他引脚接错电压轻则无响应重则烧毁。使用I2C扫描器代码扫描总线上的设备地址。如果扫出0x57说明传感器在线问题在调用库的逻辑如果什么都扫不到大概率是硬件连接问题。I2C扫描代码很短网上随手就能找到核心逻辑是在setup里执行Wire.begin()后遍历地址0x01到0x7F调用Wire.endTransmission()看返回值是否为0。扫描出0x57就意味着传感器已被正确识别。6.2 BPM乱跳数值忽高忽低这是所有DIY心率项目最头疼的问题我自己调试时也困扰了很久。总结下来原因一般出在三个方面第一手指没有放稳。测量过程中手部肌肉的微小颤动会叠加到波形上峰检测器可能把一个抖动误认为一次心跳导致BPM突然飙升。解法是让手搭在桌面上保持放松。第二波形幅值太小。IR信号如果整体偏低算法的信噪比就差。可以通过适当增大红光LED电流来改善把setPulseAmplitudeRed(0x0A)改成0x1F试试。第三平均窗口太短。代码里的RATE_SIZE是4意思是只取最近4个间隔取平均。如果瞬时跳动频繁可以增加到8或16平滑效果会更好代价是实时性略有降低。还有一个细节beatsPerMinute首次检测到心跳前是0前几秒钟的BPM输出会偏大或为0这是算法预热期的正常现象不要把它当作问题。6.3 IR值稳定不变像死了一样这种情况多半不是算法问题而是光线根本没打出来或者没反射回来。检查模块上LED是否发光——黑暗中用手机摄像头看MAX30102的窗口应该能看到红光在闪烁。如果完全不发光检查供电和模块上的使能引脚是否接对。有些模块的INT引脚不接也能用但个别版本可能需要把某些引脚上拉。还有一种常见情况模块上有透明保护膜没有撕掉。新出厂的MAX30102模块会在LED窗口贴一层薄薄的膜发货时用来防划伤但这层膜会严重削弱光的反射和接收。我第一次用的时候就犯过这个错误读数微弱得根本没法用。6.4 串口输出乱码乱码基本都是波特率不匹配引起的。代码里Serial.begin(115200)但串口监视器右下角选成了9600就会看到一堆乱码。把串口监视器的波特率改成115200即可。另外注意ESP32有两个串口Serial走的是USB转串口芯片Serial1和Serial2走的是板载引脚。本项目只用Serial所以串口监视器里能看到数据如果你后续想接OLED屏或者蓝牙模块要注意别和USB口抢占资源。6.5 模块发热严重如果模块表面有明显发热先量一下供电电压。MAX30102的VIN理论上最高可以承受5V但发热说明功耗异常大概率是IO口接错——比如把SDA接到了一个输出高电平的引脚上灌入过大的电流。ESP32的GPIO输出能力有限传感器模块的负载也很轻正常工作时应该是微温甚至无感的。7. 项目扩展从一块开发板到一套心率系统走完上面所有步骤你已经完成了一个完整的传感器数据采集项目这很重要——它验证了你在嵌入式领域最基础的能力读数据、算数据、看数据。但如果你觉得还不过瘾这个项目有很多自然的延伸方向。比如把0.91英寸的OLED小屏幕接上串口数据就能直接显示在屏幕上变成一台独立的心率计。OLED屏同样走I2C协议可以和MAX30102挂在同一条总线上只要给它们分配不同的设备地址即可。代码层面只是多套一个屏幕显示库复杂度不大。再接一个按键模块还能做成“按下测量、松开停止”的交互逻辑。再比如利用ESP32自带的WiFi和蓝牙把心率数据上报到云端或者做成一个手机App实时查看的便携设备。ESP32做OTA升级也很方便后续改动程序不用插线直接通过网络烧录新代码。这些扩展都需要你已经能熟练完成本项目为目标可以说MAX30102是我推荐的ESP32入门最佳实践之一——它不挑硬件、逻辑清晰、见效快而且学到的I2C通信、传感器驱动、信号处理思维换到其他传感器项目里完全复用得上。我自己是在把这块板子玩转之后才真正理解了“硬件工程师和程序员没什么区别”这句话。你以为你在写代码其实你在和物理世界打交道。MAX30102让你用最直接的方式体会到一个物理信号如何变成一串在你眼前跳动的数字。这种成就感是某些纯软件项目很难给的。所以别嫌这个项目简单动手把它跑通你会获得比看十篇教程都大的收获。
分享:

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

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