1. 项目概述一个能感知心跳的毛绒伙伴几年前我在一个创客展上看到一对异地恋情侣的分享他们希望能有一种更温暖的方式感知对方的“存在感”。这个想法一直留在我心里直到我接触了CircuitPython和MQTT一个将心跳“可视化”和“触觉化”的创意终于有了落地的可能。这就是“心跳毛绒玩具”项目的由来制作两个独立的毛绒玩具每个玩具都能实时监测佩戴者的心率并通过无线网络将数据发送给对方。对方的玩具则会以相同的节奏闪烁LED并产生振动让相隔两地的人能以一种独特而私密的方式感受到彼此生命的律动。这个项目的核心价值在于它巧妙地将生物传感器、嵌入式硬件、无线通信和情感化设计融为一体是一个典型的物联网与嵌入式开发交叉的实践案例。它不仅仅是一个技术Demo更是一个有温度的应用。对于学习者而言你将亲自动手串联起从传感器数据采集MAX30102、嵌入式逻辑处理Arduino micro:bit、到无线数据传输蓝牙 MQTT以及最终执行器反馈马达LED的完整物联网链路。无论你是想深入理解物联网架构还是寻找一个充满温情的硬件项目这都将是一次收获颇丰的实践。2. 核心硬件选型与设计思路拆解2.1 主控板为什么是micro:bit V2 Arduino Pro Micro的双核架构原项目作者提到了“为了 affordability可负担性”选择了micro:bit V2。这确实是个明智的选择。micro:bit V2价格亲民内置了5x5红色LED矩阵、蓝牙、加速度计和麦克风且官方支持CircuitPython极大地降低了开发门槛。但这里有一个关键限制计算能力。MAX30102心率传感器输出的原始数据是光电脉搏波PPG信号要从中准确计算出心率BPM需要进行滤波、寻峰、计算间隔等一系列数字信号处理DSP操作。这对主频仅64MHz、内存有限的micro:bit来说负担过重可能导致计算延迟、不准确甚至系统卡顿。注意许多新手会试图在单一低功耗微控制器上同时完成传感器数据采集、复杂算法处理和无线通信这往往会导致系统不稳定。合理的架构设计是项目成功的第一步。因此项目采用了双核架构进行职责分离Arduino Pro Micro扮演“传感与计算单元”。它通过I2C接口读取MAX30102的原始数据利用其相对更强的处理能力ATmega32U416MHz运行心率计算算法并将计算好的、纯净的BPM数值通过串口输出。这保证了心率计算的实时性和准确性。micro:bit V2扮演“通信与交互单元”。它通过串口从Arduino接收BPM数据然后专注于其擅长的任务通过蓝牙将数据发送到手机App并通过MQTT协议转发至云端同时根据接收到的远端心率数据驱动LED矩阵闪烁和控制振动马达。它的内置蓝牙和LED矩阵正好物尽其用。这种架构的优势在于稳定性与模块化。两个核心各司其职互不干扰。未来如果你想升级心率算法只需更新Arduino代码如果想改变交互逻辑如LED动画只需修改CircuitPython代码。2.2 传感器与执行器关键部件的选型考量MAX30102心率血氧传感器这是项目的“心脏”。选择它而非更便宜的MAX30100或模拟式心率传感器主要因为其集成度高、自带环境光消除算法且同时支持心率和血氧测量本项目仅用心率。其I2C接口使其与Arduino的连接变得非常简洁。需要注意的是它对于佩戴位置和压力比较敏感后期需要我们在软件和结构上做优化。DRV2605L触觉驱动器与LRA马达为什么不直接用micro:bit的IO口驱动一个普通振动马达因为触觉体验至关重要。普通马达振动生硬、噪音大。DRV2605L是一款专业的触觉驱动芯片可以驱动LRA线性谐振执行器或ERM偏心转子马达。LRA马达的振动更细腻、响应更快、功耗更低能模拟出更真实的“心跳”触感。DRV2605L通过I2C控制可以编程实现多种复杂的振动效果这里我们用它来精确复现心跳的节奏和强度。电源方案项目使用micro:bit的电池包为整个系统供电。这里有一个细节Arduino Pro Micro和micro:bit都需要3.3V或5V供电。通过 wiring diagram 可以看到作者巧妙地通过“三通连接”将电池正负极同时引给两个板子。务必确保电源极性正确且电池容量足够建议使用3节AAA电池或一块小型锂电池。3. 软件环境搭建与核心代码解析3.1 CircuitPython在micro:bit上的部署与文件管理micro:bit刷入CircuitPython后其文件系统管理与常见的ESP32或RP2040开发板不同。它不会在电脑上显示为一个U盘Mass Storage Device因此无法直接拖拽文件。这就需要用到adafruit-ampy工具。# 安装 ampy pip install adafruit-ampy # 列出micro:bit上的文件确认端口Windows可能是COMXLinux/macOS是/dev/ttyACM0 ampy --port /dev/ttyACM0 ls / # 上传整个lib库目录包含所有依赖的.mpy文件 ampy --port /dev/ttyACM0 put lib /lib # 上传主程序代码并重命名为code.pyCircuitPython启动自动运行的文件 ampy --port /dev/ttyACM0 put code.min.py /code.py实操心得原项目提到为了节省micro:bit的有限存储空间删除了adafruit_ble库中一些不用的特性文件如sphero.mpy。这是一个非常实用的技巧。在空间紧张的设备上我们可以手动检查lib目录移除项目用不到的库文件。例如如果我们只用蓝牙UART那么adafruit_ble中与MIDI、Microbit特定服务相关的文件就可以删除。3.2 Arduino心率算法解析与代码要点Arduino端的代码是项目的“大脑”。其核心任务是从MAX30102读取数据并计算心率。一个典型的流程如下初始化与配置设置I2C配置MAX30102的采样率、LED脉冲宽度和电流。较高的采样率如100Hz能捕捉更细致的脉搏波但会增加数据量。数据采集循环在loop()函数中不断检查传感器是否有新数据。读取红外IR和红光Red两个通道的ADC值。通常使用IR通道进行心率计算因为它受血液氧合度影响较小信号更稳定。信号处理与心率计算滤波原始PPG信号含有大量噪声如运动伪影、电源工频干扰。首先需要通过软件滤波器如带通滤波器通常通带为0.5Hz ~ 5Hz对应心率30-300 BPM进行初步平滑。寻峰在滤波后的信号中识别出每个脉搏波的峰值点。算法需要能避免因噪声引起的误判。计算间隔记录连续两个峰值之间的时间间隔单位毫秒。计算BPM心率BPM 60000 / 峰值间隔(ms)。为了结果更稳定通常取最近几次如4次间隔的平均值来计算BPM。// 伪代码逻辑示意 long lastBeatTime 0; float beatsPerMinute; int beatInterval; void loop() { if (sensor.checkForData()) { long redValue sensor.getRed(); long irValue sensor.getIR(); // 主要使用IR值 // 1. 将irValue送入滤波器 float filteredIR bandpassFilter(irValue); // 2. 寻峰算法 if (isPeak(filteredIR)) { // 3. 计算时间间隔 beatInterval millis() - lastBeatTime; lastBeatTime millis(); // 4. 计算BPM简单平均 if (beatInterval 0) { beatsPerMinute 60000 / beatInterval; // 可以通过串口发送 beatsPerMinute Serial.println(beatsPerMinute); } } } }注意事项心率算法是项目的难点。网上有开源的MAX30102库如SparkFun_MAX3010x库它们可能内置了基础的心率计算功能但效果在运动或佩戴不佳时可能不稳定。对于追求精度的项目可以考虑使用更成熟的算法如PulseSensor社区的开源算法或者投入时间研究数字信号处理。3.3 CircuitPython端的通信与交互逻辑micro:bit上的code.py主要负责通信和驱动。其逻辑流如下初始化初始化串口用于与Arduino通信、蓝牙UART服务、I2C用于连接DRV2605L、以及板载LED矩阵。双循环任务读取串口不断检查串口是否有来自Arduino的新心率数据。一旦收到将其转换为整数。蓝牙/MQTT发送将收到的心率数据通过蓝牙UART发送到已连接的手机Bluefruit Connect App。App会将其发布到指定的Adafruit IO Feed如hbplush1。蓝牙/MQTT接收同时监听来自手机App的UART数据这些数据是来自云端另一个玩具的心率信息。执行反馈根据接收到的远端心率数据进行两项操作 a.LED动画计算心跳间隔控制5x5 LED矩阵以“收缩-膨胀”或“波浪”动画形式闪烁频率与心率同步。 b.触觉反馈通过I2C向DRV2605L发送指令驱动LRA马达振动。可以编程让DRV2605L播放一个简短的“单击”效果并精确控制每次振动之间的间隔模拟心跳的“咚、咚”感。# 代码结构示意 (code.py) import board import busio import digitalio import time from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement from adafruit_ble.services.nordic import UARTService # 初始化 i2c busio.I2C(board.SCL, board.SDA) ble BLERadio() uart UARTService() advertisement ProvideServicesAdvertisement(uart) # 主循环 while True: # 1. 广播并等待手机连接 ble.start_advertising(advertisement) while not ble.connected: pass # 2. 连接后进行数据收发 while ble.connected: # 2.1 读取串口来自Arduino的心率 if uart.in_waiting: local_bpm int(uart.read()) # 将 local_bpm 通过MQTT主题发送出去由App完成 uart.write(f{local_bpm}\n) # 2.2 检查是否有来自手机云端的数据 if uart.in_waiting: remote_bpm_data uart.readline() if remote_bpm_data: remote_bpm int(remote_bpm_data) # 根据remote_bpm控制LED和马达 heartbeat_interval_ms 60000 / remote_bpm # 触发LED闪烁 show_heart_animation() # 触发马达振动 drv2605l.play_effect(1) # 播放预置效果1 time.sleep(heartbeat_interval_ms / 1000)4. 硬件焊接与组装实操详解4.1 micro:bit的精细焊接与上拉电阻处理micro:bit的焊盘很小尤其是背面的“金手指”焊盘。原项目作者给出了一个非常关键但容易出错的步骤焊接大焊盘首先在正面标有“0”、“1”、“2”、“3V”、“GND”等的大焊盘上焊接导线。这些焊盘面积大易于操作。务必使用尖头烙铁和细焊锡丝。背面上拉电阻的飞线这是难点。为了给I2C用于DRV2605L提供上拉电阻作者没有使用micro:bit内置的I2C引脚P19/P20因为它们的焊盘太小。而是选择了通用的GPIO引脚P0和P1。步骤在背面找到P0和P1对应的细小焊盘。先在P3焊盘上贴一小块电工胶带做绝缘防止短路。然后取一个10kΩ电阻一端焊接在P1焊盘上另一端需要连接到3.3V电源。但P1焊盘附近没有方便的3.3V点所以作者用了一个巧妙的“飞线”方法将电阻悬空的那端在电阻体本身之前用胶带固定绝缘然后用另一根导线从电阻的悬空端引到正面的3.3V焊盘。对P0焊盘重复此操作。替代方案更推荐如果你觉得飞线难度太高可以采用“面包板过渡”或“模块集成”方案。例如使用一个微型穿孔板先将两个10kΩ电阻焊在上面一端并联接到3.3V另一端分别引出两根线准备接P0和P1。然后将这个微型板子固定在micro:bit背面再用细导线连接P0和P1。这样更规整也便于调试。4.2 系统集成与布线技巧按照接线图连接所有部件时混乱的线缆是调试的噩梦。以下是一些实操建议颜色规范统一线缆颜色。例如红色-VCC/3.3V/5V黑色-GND黄色-SCL绿色-SDA蓝色-TX白色-RX。这能极大减少接错线的概率。电源优先先确保所有模块的电源VCC和地GND正确连接并通电正常再连接信号线SDA, SCL, TX, RX。分模块测试不要一次性焊完所有线。可以分步测试先只连接Arduino和MAX30102上传测试代码在串口监视器查看能否读到原始数据。再单独测试micro:bit与DRV2605L和马达写个简单脚本看能否驱动振动。最后连接Arduino的TX到micro:bit的RXP3测试串口通信。绝缘与固定所有焊接点、裸露的导线接头都必须用电工胶带或热缩管进行绝缘处理。最后可以使用尼龙扎带或胶枪将线缆整理固定在开发板背面防止移动时扯断。5. 云端服务配置与移动端联动5.1 Adafruit IO Feed与MQTT主题规划Adafruit IO是一个非常适合创客的物联网平台提供了直观的Feed数据流管理和MQTT代理服务。这里的配置逻辑是“交叉订阅”玩具A订阅Subscribehbplush2这个Feed以接收玩具B的心率数据发布Publish自己的心率数据到hbplush1。玩具B订阅Subscribehbplush1发布到hbplush2。在Adafruit IO上创建Feed时名字可以自定义但两个玩具的代码和App配置中必须使用一致的主题名。MQTT主题的格式通常是你的用户名/feeds/feed名称。5.2 Bluefruit Connect App的桥梁作用micro:bit通过蓝牙低功耗BLE与手机连接。Adafruit的Bluefruit Connect App在这里扮演了协议转换网关的角色。连接App通过BLE UART服务与micro:bit建立双向通信通道。配置在App的UART/MQTT设置界面你需要进行关键配置UART RXApp接收micro:bit数据的通道设置为用户名/feeds/hbplush1。这意味着micro:bit通过串口发送过来的数据本地心率会被App自动通过MQTT发布到hbplush1这个Feed。UART TXApp向micro:bit发送数据的通道设置一个MQTT主题例如用户名/feeds/hbplush2并勾选“Transmit”。这意味着当有任何数据发布到hbplush2这个Feed时即玩具B的心率App会自动通过BLE UART将其发送给micro:bit。密钥在App中填入你的Adafruit IO用户名和Active Key在网站“My Key”页面获取以完成MQTT连接认证。常见问题如果数据无法同步请按以下顺序排查① micro:bit与App蓝牙是否已配对并显示“Connected”② App内的MQTT主题配置是否正确特别是用户名③ Adafruit IO的Active Key是否已正确填入App④ 两个玩具的Feed名称是否配置成了交叉订阅6. 系统调试与优化实录6.1 心率传感器读数不稳定怎么办MAX30102对佩戴条件非常敏感。以下是提升读数稳定性的技巧物理接触确保传感器紧贴皮肤但不要过度按压导致毛细血管被压闭。可以使用一个柔软的泡沫垫或硅胶套来改善接触并隔离环境光。软件滤波在Arduino端加强软件滤波。除了基本的低通或带通滤波可以加入移动平均或中值滤波来消除突发噪声。信号质量检测在算法中加入信号质量判断。例如计算一段时间内红外信号的AC/DC分量比值或检测峰值幅度的稳定性。只有当信号质量高于阈值时才输出心率值否则输出一个错误码如0或-1并在micro:bit端让LED显示“X”图案提示佩戴不佳。环境光补偿虽然MAX30102自带环境光消除但在强光直射下仍可能失效。尽量在室内或光线均匀的环境下使用。6.2 蓝牙或MQTT连接断断续续蓝牙距离与干扰确保手机与micro:bit的距离在BLE有效范围内通常10米内无障碍。远离Wi-Fi路由器、微波炉等2.4GHz干扰源。电源噪声马达振动时可能会引起电源电压波动干扰蓝牙芯片工作。在电机电源两端并联一个100μF的电解电容和一个0.1μF的陶瓷电容可以起到很好的稳压和滤波作用。Adafruit IO连接限制免费版的Adafruit IO有连接数和数据点速率的限制。如果频繁断开检查是否超出限制。两个玩具的数据更新间隔不宜太快心率每1-2秒发送一次即可。App后台运行确保手机系统没有强制关闭Bluefruit Connect App的后台进程。在手机设置中给予该App“后台运行”或“电池优化忽略”的权限。6.3 触觉反馈不自然DRV2605L效果库DRV2605L内置了一个效果库ROM Library包含上百种预定义的振动效果如“单击”、“双击”、“强劲点击”等。通过I2C命令调用不同的效果索引可以找到最接近心跳感觉的效果通常是效果1“Strong Click”。自定义波形如果内置效果不满意可以切换到“实时播放”模式直接向驱动器发送PWM波形数据自定义振动的强度、时长和模式模拟出更逼真的“扑通”感。同步性确保LED闪烁和马达振动的触发是严格同步的且间隔时间是根据接收到的准确BPM计算得出的。避免使用time.sleep()造成阻塞建议使用非阻塞的时间戳比对方式来控制节奏。7. 项目扩展与进阶玩法完成基础功能后这个项目还有巨大的扩展空间数据可视化与记录利用Adafruit IO的Dashboard功能为两个Feed创建实时图表可以远程、历史性地查看彼此的心率变化成为一份独特的健康日记。加入血氧监测MAX30102本身支持血氧饱和度SpO2测量。可以修改Arduino代码将血氧数据也计算出来并通过另一个Feed发送。在接收端可以用LED矩阵的不同颜色或图案来表示心率红色和血氧蓝色。本地直接通信去云端化如果希望两个玩具在无互联网环境下如在同一房间内也能工作可以探索让两个micro:bit通过蓝牙直接建立连接BLE Peripheral/Central模式互相发送数据绕过手机和云端。这能降低延迟但需要编写更复杂的蓝牙配对和通信逻辑。外观与结构设计将电路巧妙地嵌入毛绒玩具内部是另一门艺术。使用柔软的硅胶套或3D打印一个带有卡槽的内壳来固定电路板用导电缝纫线代替部分导线以增加柔软度并将振动马达缝制在玩具心脏位置LED矩阵透过玩具眼睛或胸前的一个半透明窗口显示都能极大提升成品的完成度和情感价值。这个项目从电路焊接、代码编写到云端配置贯穿了现代物联网产品开发的核心流程。它遇到的每一个问题——信号干扰、电源管理、无线连接、用户体验——都是真实产品开发中的缩影。当你最终看到两个毛绒玩具随着彼此的心跳而同步闪烁和振动时那种技术创造情感的成就感远非一个简单的流水灯项目所能比拟。
基于双核架构的心率感知物联网玩具:从传感器到云端的情感化硬件实践
发布时间:2026/6/1 11:52:19
1. 项目概述一个能感知心跳的毛绒伙伴几年前我在一个创客展上看到一对异地恋情侣的分享他们希望能有一种更温暖的方式感知对方的“存在感”。这个想法一直留在我心里直到我接触了CircuitPython和MQTT一个将心跳“可视化”和“触觉化”的创意终于有了落地的可能。这就是“心跳毛绒玩具”项目的由来制作两个独立的毛绒玩具每个玩具都能实时监测佩戴者的心率并通过无线网络将数据发送给对方。对方的玩具则会以相同的节奏闪烁LED并产生振动让相隔两地的人能以一种独特而私密的方式感受到彼此生命的律动。这个项目的核心价值在于它巧妙地将生物传感器、嵌入式硬件、无线通信和情感化设计融为一体是一个典型的物联网与嵌入式开发交叉的实践案例。它不仅仅是一个技术Demo更是一个有温度的应用。对于学习者而言你将亲自动手串联起从传感器数据采集MAX30102、嵌入式逻辑处理Arduino micro:bit、到无线数据传输蓝牙 MQTT以及最终执行器反馈马达LED的完整物联网链路。无论你是想深入理解物联网架构还是寻找一个充满温情的硬件项目这都将是一次收获颇丰的实践。2. 核心硬件选型与设计思路拆解2.1 主控板为什么是micro:bit V2 Arduino Pro Micro的双核架构原项目作者提到了“为了 affordability可负担性”选择了micro:bit V2。这确实是个明智的选择。micro:bit V2价格亲民内置了5x5红色LED矩阵、蓝牙、加速度计和麦克风且官方支持CircuitPython极大地降低了开发门槛。但这里有一个关键限制计算能力。MAX30102心率传感器输出的原始数据是光电脉搏波PPG信号要从中准确计算出心率BPM需要进行滤波、寻峰、计算间隔等一系列数字信号处理DSP操作。这对主频仅64MHz、内存有限的micro:bit来说负担过重可能导致计算延迟、不准确甚至系统卡顿。注意许多新手会试图在单一低功耗微控制器上同时完成传感器数据采集、复杂算法处理和无线通信这往往会导致系统不稳定。合理的架构设计是项目成功的第一步。因此项目采用了双核架构进行职责分离Arduino Pro Micro扮演“传感与计算单元”。它通过I2C接口读取MAX30102的原始数据利用其相对更强的处理能力ATmega32U416MHz运行心率计算算法并将计算好的、纯净的BPM数值通过串口输出。这保证了心率计算的实时性和准确性。micro:bit V2扮演“通信与交互单元”。它通过串口从Arduino接收BPM数据然后专注于其擅长的任务通过蓝牙将数据发送到手机App并通过MQTT协议转发至云端同时根据接收到的远端心率数据驱动LED矩阵闪烁和控制振动马达。它的内置蓝牙和LED矩阵正好物尽其用。这种架构的优势在于稳定性与模块化。两个核心各司其职互不干扰。未来如果你想升级心率算法只需更新Arduino代码如果想改变交互逻辑如LED动画只需修改CircuitPython代码。2.2 传感器与执行器关键部件的选型考量MAX30102心率血氧传感器这是项目的“心脏”。选择它而非更便宜的MAX30100或模拟式心率传感器主要因为其集成度高、自带环境光消除算法且同时支持心率和血氧测量本项目仅用心率。其I2C接口使其与Arduino的连接变得非常简洁。需要注意的是它对于佩戴位置和压力比较敏感后期需要我们在软件和结构上做优化。DRV2605L触觉驱动器与LRA马达为什么不直接用micro:bit的IO口驱动一个普通振动马达因为触觉体验至关重要。普通马达振动生硬、噪音大。DRV2605L是一款专业的触觉驱动芯片可以驱动LRA线性谐振执行器或ERM偏心转子马达。LRA马达的振动更细腻、响应更快、功耗更低能模拟出更真实的“心跳”触感。DRV2605L通过I2C控制可以编程实现多种复杂的振动效果这里我们用它来精确复现心跳的节奏和强度。电源方案项目使用micro:bit的电池包为整个系统供电。这里有一个细节Arduino Pro Micro和micro:bit都需要3.3V或5V供电。通过 wiring diagram 可以看到作者巧妙地通过“三通连接”将电池正负极同时引给两个板子。务必确保电源极性正确且电池容量足够建议使用3节AAA电池或一块小型锂电池。3. 软件环境搭建与核心代码解析3.1 CircuitPython在micro:bit上的部署与文件管理micro:bit刷入CircuitPython后其文件系统管理与常见的ESP32或RP2040开发板不同。它不会在电脑上显示为一个U盘Mass Storage Device因此无法直接拖拽文件。这就需要用到adafruit-ampy工具。# 安装 ampy pip install adafruit-ampy # 列出micro:bit上的文件确认端口Windows可能是COMXLinux/macOS是/dev/ttyACM0 ampy --port /dev/ttyACM0 ls / # 上传整个lib库目录包含所有依赖的.mpy文件 ampy --port /dev/ttyACM0 put lib /lib # 上传主程序代码并重命名为code.pyCircuitPython启动自动运行的文件 ampy --port /dev/ttyACM0 put code.min.py /code.py实操心得原项目提到为了节省micro:bit的有限存储空间删除了adafruit_ble库中一些不用的特性文件如sphero.mpy。这是一个非常实用的技巧。在空间紧张的设备上我们可以手动检查lib目录移除项目用不到的库文件。例如如果我们只用蓝牙UART那么adafruit_ble中与MIDI、Microbit特定服务相关的文件就可以删除。3.2 Arduino心率算法解析与代码要点Arduino端的代码是项目的“大脑”。其核心任务是从MAX30102读取数据并计算心率。一个典型的流程如下初始化与配置设置I2C配置MAX30102的采样率、LED脉冲宽度和电流。较高的采样率如100Hz能捕捉更细致的脉搏波但会增加数据量。数据采集循环在loop()函数中不断检查传感器是否有新数据。读取红外IR和红光Red两个通道的ADC值。通常使用IR通道进行心率计算因为它受血液氧合度影响较小信号更稳定。信号处理与心率计算滤波原始PPG信号含有大量噪声如运动伪影、电源工频干扰。首先需要通过软件滤波器如带通滤波器通常通带为0.5Hz ~ 5Hz对应心率30-300 BPM进行初步平滑。寻峰在滤波后的信号中识别出每个脉搏波的峰值点。算法需要能避免因噪声引起的误判。计算间隔记录连续两个峰值之间的时间间隔单位毫秒。计算BPM心率BPM 60000 / 峰值间隔(ms)。为了结果更稳定通常取最近几次如4次间隔的平均值来计算BPM。// 伪代码逻辑示意 long lastBeatTime 0; float beatsPerMinute; int beatInterval; void loop() { if (sensor.checkForData()) { long redValue sensor.getRed(); long irValue sensor.getIR(); // 主要使用IR值 // 1. 将irValue送入滤波器 float filteredIR bandpassFilter(irValue); // 2. 寻峰算法 if (isPeak(filteredIR)) { // 3. 计算时间间隔 beatInterval millis() - lastBeatTime; lastBeatTime millis(); // 4. 计算BPM简单平均 if (beatInterval 0) { beatsPerMinute 60000 / beatInterval; // 可以通过串口发送 beatsPerMinute Serial.println(beatsPerMinute); } } } }注意事项心率算法是项目的难点。网上有开源的MAX30102库如SparkFun_MAX3010x库它们可能内置了基础的心率计算功能但效果在运动或佩戴不佳时可能不稳定。对于追求精度的项目可以考虑使用更成熟的算法如PulseSensor社区的开源算法或者投入时间研究数字信号处理。3.3 CircuitPython端的通信与交互逻辑micro:bit上的code.py主要负责通信和驱动。其逻辑流如下初始化初始化串口用于与Arduino通信、蓝牙UART服务、I2C用于连接DRV2605L、以及板载LED矩阵。双循环任务读取串口不断检查串口是否有来自Arduino的新心率数据。一旦收到将其转换为整数。蓝牙/MQTT发送将收到的心率数据通过蓝牙UART发送到已连接的手机Bluefruit Connect App。App会将其发布到指定的Adafruit IO Feed如hbplush1。蓝牙/MQTT接收同时监听来自手机App的UART数据这些数据是来自云端另一个玩具的心率信息。执行反馈根据接收到的远端心率数据进行两项操作 a.LED动画计算心跳间隔控制5x5 LED矩阵以“收缩-膨胀”或“波浪”动画形式闪烁频率与心率同步。 b.触觉反馈通过I2C向DRV2605L发送指令驱动LRA马达振动。可以编程让DRV2605L播放一个简短的“单击”效果并精确控制每次振动之间的间隔模拟心跳的“咚、咚”感。# 代码结构示意 (code.py) import board import busio import digitalio import time from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement from adafruit_ble.services.nordic import UARTService # 初始化 i2c busio.I2C(board.SCL, board.SDA) ble BLERadio() uart UARTService() advertisement ProvideServicesAdvertisement(uart) # 主循环 while True: # 1. 广播并等待手机连接 ble.start_advertising(advertisement) while not ble.connected: pass # 2. 连接后进行数据收发 while ble.connected: # 2.1 读取串口来自Arduino的心率 if uart.in_waiting: local_bpm int(uart.read()) # 将 local_bpm 通过MQTT主题发送出去由App完成 uart.write(f{local_bpm}\n) # 2.2 检查是否有来自手机云端的数据 if uart.in_waiting: remote_bpm_data uart.readline() if remote_bpm_data: remote_bpm int(remote_bpm_data) # 根据remote_bpm控制LED和马达 heartbeat_interval_ms 60000 / remote_bpm # 触发LED闪烁 show_heart_animation() # 触发马达振动 drv2605l.play_effect(1) # 播放预置效果1 time.sleep(heartbeat_interval_ms / 1000)4. 硬件焊接与组装实操详解4.1 micro:bit的精细焊接与上拉电阻处理micro:bit的焊盘很小尤其是背面的“金手指”焊盘。原项目作者给出了一个非常关键但容易出错的步骤焊接大焊盘首先在正面标有“0”、“1”、“2”、“3V”、“GND”等的大焊盘上焊接导线。这些焊盘面积大易于操作。务必使用尖头烙铁和细焊锡丝。背面上拉电阻的飞线这是难点。为了给I2C用于DRV2605L提供上拉电阻作者没有使用micro:bit内置的I2C引脚P19/P20因为它们的焊盘太小。而是选择了通用的GPIO引脚P0和P1。步骤在背面找到P0和P1对应的细小焊盘。先在P3焊盘上贴一小块电工胶带做绝缘防止短路。然后取一个10kΩ电阻一端焊接在P1焊盘上另一端需要连接到3.3V电源。但P1焊盘附近没有方便的3.3V点所以作者用了一个巧妙的“飞线”方法将电阻悬空的那端在电阻体本身之前用胶带固定绝缘然后用另一根导线从电阻的悬空端引到正面的3.3V焊盘。对P0焊盘重复此操作。替代方案更推荐如果你觉得飞线难度太高可以采用“面包板过渡”或“模块集成”方案。例如使用一个微型穿孔板先将两个10kΩ电阻焊在上面一端并联接到3.3V另一端分别引出两根线准备接P0和P1。然后将这个微型板子固定在micro:bit背面再用细导线连接P0和P1。这样更规整也便于调试。4.2 系统集成与布线技巧按照接线图连接所有部件时混乱的线缆是调试的噩梦。以下是一些实操建议颜色规范统一线缆颜色。例如红色-VCC/3.3V/5V黑色-GND黄色-SCL绿色-SDA蓝色-TX白色-RX。这能极大减少接错线的概率。电源优先先确保所有模块的电源VCC和地GND正确连接并通电正常再连接信号线SDA, SCL, TX, RX。分模块测试不要一次性焊完所有线。可以分步测试先只连接Arduino和MAX30102上传测试代码在串口监视器查看能否读到原始数据。再单独测试micro:bit与DRV2605L和马达写个简单脚本看能否驱动振动。最后连接Arduino的TX到micro:bit的RXP3测试串口通信。绝缘与固定所有焊接点、裸露的导线接头都必须用电工胶带或热缩管进行绝缘处理。最后可以使用尼龙扎带或胶枪将线缆整理固定在开发板背面防止移动时扯断。5. 云端服务配置与移动端联动5.1 Adafruit IO Feed与MQTT主题规划Adafruit IO是一个非常适合创客的物联网平台提供了直观的Feed数据流管理和MQTT代理服务。这里的配置逻辑是“交叉订阅”玩具A订阅Subscribehbplush2这个Feed以接收玩具B的心率数据发布Publish自己的心率数据到hbplush1。玩具B订阅Subscribehbplush1发布到hbplush2。在Adafruit IO上创建Feed时名字可以自定义但两个玩具的代码和App配置中必须使用一致的主题名。MQTT主题的格式通常是你的用户名/feeds/feed名称。5.2 Bluefruit Connect App的桥梁作用micro:bit通过蓝牙低功耗BLE与手机连接。Adafruit的Bluefruit Connect App在这里扮演了协议转换网关的角色。连接App通过BLE UART服务与micro:bit建立双向通信通道。配置在App的UART/MQTT设置界面你需要进行关键配置UART RXApp接收micro:bit数据的通道设置为用户名/feeds/hbplush1。这意味着micro:bit通过串口发送过来的数据本地心率会被App自动通过MQTT发布到hbplush1这个Feed。UART TXApp向micro:bit发送数据的通道设置一个MQTT主题例如用户名/feeds/hbplush2并勾选“Transmit”。这意味着当有任何数据发布到hbplush2这个Feed时即玩具B的心率App会自动通过BLE UART将其发送给micro:bit。密钥在App中填入你的Adafruit IO用户名和Active Key在网站“My Key”页面获取以完成MQTT连接认证。常见问题如果数据无法同步请按以下顺序排查① micro:bit与App蓝牙是否已配对并显示“Connected”② App内的MQTT主题配置是否正确特别是用户名③ Adafruit IO的Active Key是否已正确填入App④ 两个玩具的Feed名称是否配置成了交叉订阅6. 系统调试与优化实录6.1 心率传感器读数不稳定怎么办MAX30102对佩戴条件非常敏感。以下是提升读数稳定性的技巧物理接触确保传感器紧贴皮肤但不要过度按压导致毛细血管被压闭。可以使用一个柔软的泡沫垫或硅胶套来改善接触并隔离环境光。软件滤波在Arduino端加强软件滤波。除了基本的低通或带通滤波可以加入移动平均或中值滤波来消除突发噪声。信号质量检测在算法中加入信号质量判断。例如计算一段时间内红外信号的AC/DC分量比值或检测峰值幅度的稳定性。只有当信号质量高于阈值时才输出心率值否则输出一个错误码如0或-1并在micro:bit端让LED显示“X”图案提示佩戴不佳。环境光补偿虽然MAX30102自带环境光消除但在强光直射下仍可能失效。尽量在室内或光线均匀的环境下使用。6.2 蓝牙或MQTT连接断断续续蓝牙距离与干扰确保手机与micro:bit的距离在BLE有效范围内通常10米内无障碍。远离Wi-Fi路由器、微波炉等2.4GHz干扰源。电源噪声马达振动时可能会引起电源电压波动干扰蓝牙芯片工作。在电机电源两端并联一个100μF的电解电容和一个0.1μF的陶瓷电容可以起到很好的稳压和滤波作用。Adafruit IO连接限制免费版的Adafruit IO有连接数和数据点速率的限制。如果频繁断开检查是否超出限制。两个玩具的数据更新间隔不宜太快心率每1-2秒发送一次即可。App后台运行确保手机系统没有强制关闭Bluefruit Connect App的后台进程。在手机设置中给予该App“后台运行”或“电池优化忽略”的权限。6.3 触觉反馈不自然DRV2605L效果库DRV2605L内置了一个效果库ROM Library包含上百种预定义的振动效果如“单击”、“双击”、“强劲点击”等。通过I2C命令调用不同的效果索引可以找到最接近心跳感觉的效果通常是效果1“Strong Click”。自定义波形如果内置效果不满意可以切换到“实时播放”模式直接向驱动器发送PWM波形数据自定义振动的强度、时长和模式模拟出更逼真的“扑通”感。同步性确保LED闪烁和马达振动的触发是严格同步的且间隔时间是根据接收到的准确BPM计算得出的。避免使用time.sleep()造成阻塞建议使用非阻塞的时间戳比对方式来控制节奏。7. 项目扩展与进阶玩法完成基础功能后这个项目还有巨大的扩展空间数据可视化与记录利用Adafruit IO的Dashboard功能为两个Feed创建实时图表可以远程、历史性地查看彼此的心率变化成为一份独特的健康日记。加入血氧监测MAX30102本身支持血氧饱和度SpO2测量。可以修改Arduino代码将血氧数据也计算出来并通过另一个Feed发送。在接收端可以用LED矩阵的不同颜色或图案来表示心率红色和血氧蓝色。本地直接通信去云端化如果希望两个玩具在无互联网环境下如在同一房间内也能工作可以探索让两个micro:bit通过蓝牙直接建立连接BLE Peripheral/Central模式互相发送数据绕过手机和云端。这能降低延迟但需要编写更复杂的蓝牙配对和通信逻辑。外观与结构设计将电路巧妙地嵌入毛绒玩具内部是另一门艺术。使用柔软的硅胶套或3D打印一个带有卡槽的内壳来固定电路板用导电缝纫线代替部分导线以增加柔软度并将振动马达缝制在玩具心脏位置LED矩阵透过玩具眼睛或胸前的一个半透明窗口显示都能极大提升成品的完成度和情感价值。这个项目从电路焊接、代码编写到云端配置贯穿了现代物联网产品开发的核心流程。它遇到的每一个问题——信号干扰、电源管理、无线连接、用户体验——都是真实产品开发中的缩影。当你最终看到两个毛绒玩具随着彼此的心跳而同步闪烁和振动时那种技术创造情感的成就感远非一个简单的流水灯项目所能比拟。