心率监测器(可穿戴和无线使用 ECG)
组件和用品
| × | 1 | ||||
![]() |
| × | 1 | |||
| × | 1 | ||||
![]() |
| × | 1 | |||
| × | 1 |
必要的工具和机器
![]() |
|
关于这个项目
这是我的心脏监测项目的第二次迭代,前一个是在胸部显示心跳,并通过电线连接到 uECG。这看起来很酷,但根本不实用——你看不清当前到底有多少 LED 亮着,它超出了你的正常视野,并且将它连接到 uECG 设备的电线会给传感器本身带来很多问题,所以运行的时候基本是不行的。
这个版本解决了所有这些问题:它是戴在手腕上的,所以你可以在注视路面的同时看到它,而且它是无线的,所以读数不会失真,它真的适合跑步,让你跟踪心脏负荷。

1. 组件
与之前的项目一样,所有繁重的工作都由 uECG 完成 - 它测量数据并计算板载 BPM。而且,当它切换到直接链接模式时,它会通过与通用 nRF24 芯片兼容的无线电协议发送所有这些信息(连同高分辨率 ECG 数据,我们在这里没有使用)。所以第二个关键组件是 nRF24 模块。而且 Arduino Nano 的尺寸正好适合放在一个小的 LED 环下面,所以我将它用作控制器(但实际上任何东西在这里都可以正常工作)。
2. 原理图
连接 nRF24 模块并不简单,您必须连接所有 SPI 线(MISO、MOSI、SCK、CS)、芯片使能线和电源。如果你想要一个相当小的尺寸 - 必须移除所有排针,并将电线直接焊接到焊盘上。所以单独连接nRF需要7根线,14个焊点。好消息是,其他一切都很简单:LED 环需要 1 根数据线和 2 根电源线,另外 2 根电源线进入电池连接器。

接线清单如下:
nRF24 pin 1 (GND) - Arduino的GND
nRF24 pin 2 (Vcc) - Arduino的3.3v
nRF24 pin 3 (Chip Enable) - Arduino的D9
nRF24 pin 4 (SPI:CS) - Arduino's D8
nRF24 pin 5 (SPI:SCK) - Arduino's D13
nRF24 pin 6 (SPI:MOSI) - Arduino's D11
nRF24 pin 7 ( SPI:MISO) - Arduino 的 D12
LED 环电源 - Arduino 的 5V
LED 环 GND - Arduino 的 GND
LED 环 DI - Arduino 的 D5
电池正极(红色) - Arduino 的 5V
电池负极(黑色)- Arduino的GND
(注意电池需要连接器,所以它可以断开和充电)
重要提示:您不能将 MOSI、MISO、SCK 线连接到任何其他 Arduino 引脚。 SPI 硬件位于 D11、D12、D13 上,如果没有连接到那里就无法工作。所有其他引脚都可以更改(如果您在程序中进行相应更改)。
3.程序
这里唯一复杂的软件是射频通道配置。在我意识到 uECG 和 nRF24 对管道地址使用不同的位顺序之前,我花了很长时间试图让它工作。当我解决这个问题时,一切都立即开始工作:) 基本上我们只是读取传入的数据包,使用它们的第 5 个字节作为 BPM,并对其进行过滤(RF 通道是嘈杂的,所以你时不时会得到随机值而不是正确的读数,并且出于兼容性原因禁用硬件 CRC)。之后,将 BPM 转换为颜色和活动像素数,就是这样。
#include
#ifdef __AVR__
#include
#endif
#include
#include
#include
#include
int rf_cen =9; //nRF24芯片使能引脚
int rf_cs =8; //nRF24 CS pin
RF24 rf(rf_cen, rf_cs);
//管道地址 - uECG 侧硬编码
uint8_t pipe_rx[8] ={0x0E, 0xE6, 0x0D, 0xA7, 0 , 0, 0, 0};
// Arduino 上的哪个引脚连接到 NeoPixels?
#define PIN 5
// Arduino 连接了多少个 NeoPixels?
#define NUMPIXELS 16
Adafruit_NeoPixel pixel =Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
uint8_t swapbits(uint8_t a){ //uECG 管道地址使用交换位顺序
反转单个字节中的位顺序
uint8_t v =0;
if(a &0x80) v |=0x01;
if(a &0x40) v |=0x02;
if(a &0x20) v |=0x04;
if(a &0x10) v |=0x08;
if(a &0x08) v |=0x10;
if(a &0x04) v |=0x20;
if(a &0x02) v |=0x40;
if(a &0x01) v |=0x80;
return v;
}
void setup() {
pixels.begin(); // 这将初始化 NeoPixel 库。
for(int i=0;i pixels.setPixelColor(i, pixel.Color(1,1,1));
}
pixels.show();
//nRF24 需要相对较慢的 SPI,可能也可以在 2MHz 下工作
SPI.begin();
SPI.setBitOrder(MSBFIRST );
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
for(int x =0; x <8; x++) //nRF24 和 uECG 的管道地址位顺序不同
pipe_rx[x] =swapbits(pipe_rx[x]);
//配置无线参数
rf.begin();
rf.setDataRate(RF24_1MBPS);
rf.setAddressWidth(4);
rf.setChannel(22);
rf.setRetries(0, 0);
rf.setAutoAck(0);
rf.disableDynamicPayloads( );
rf.setPayloadSize(32);
rf.openReadingPipe(0, pipe_rx);
rf.setCRCLength(RF24_CRC_DISABLED);
rf.disableCRC();
rf.startListening(); //监听uECG数据
//注意uECG应该切换到原始数据模式(通过长按按钮)
//为了发送兼容的数据包,默认以BLE模式发送数据
//不能被nRF24接收
}
long last_pix_upd =0;
byte in_pack[32];
int rf_bpm =0;
int bpm_hist [5]; //由于我们禁用了CRC,需要过滤传入的数据
void loop()
{
if(rf.available())
{
rf.read( in_pack, 32);
int bb =in_pack[5]; //BPM位于数据包的第5个字节
//详细的数据包结构在uECG docs
//由于兼容性原因我们没有CRC,我们需要过滤
//传入数据,无线电信道可能有噪音。我们比较最后5个
//接收到的BPM值,只有当所有5个都相同时才使用一个。
//由于uECG每秒发送大约100个数据包,因此不会导致
//显示数据的任何明显延迟
for(int n =0; n <5-1; n++) //将 bpm 历史数组移动 1
bpm_hist[n] =bpm_hist[n+1];
bpm_hist[4] =bb; //添加新的bpm值
for(int n =0; n <5; n++) //检查是否都相等
if(bpm_hist[n] !=bb) bb =-1;
if(bb> 0) //如果是 - 将其存储为新收到的 BPM
rf_bpm =bb;
}
long ms =millis();
if( ms - last_pix_upd> 10) //不要经常更新像素
{
int r, g, b;
last_pix_upd =ms;
int bpm =rf_bpm;
int max_bright =160; //最大亮度的值,最大值为 255。但您并不总是希望它达到最大值 :)
float dd =25; //不同色调之间的 BPM 变化(蓝色->绿色->黄色->粉色->红色)
float t1 =90, t2, t3, t4; //t1 - "base" BPM, 低于 t1 为蓝色
t2 =t1 + dd;
t3 =t2 + dd;
t4 =t3 + dd;
/ /改变颜色的代码取决于我们现在所处的 t1...t4 范围
if(bpm else if(bpm else if(bpm else if(bpm else {r =max_bright;克 =0;乙 =0; }
int on_pixels =(bpm-80)/8; //因为它是用来跑步的,我不会
//显示任何低于 80 BPM 的东西,这样它在
//高负载区域更敏感
for(int i=0;i {
//像素从最后到第一个设置,没有特殊原因,
//如果从第一个到最后一个设置,效果会一样好
if(i else pixel.setPixelColor(NUMPIXELS-i-1, pixel.Color(0, 0,0)); //关闭所有其他 LED
}
pixels.show();
}
}
4. 腕带组装
当所有电线都焊接好后,程序就会闪烁,并且您确认收到了 uECG 数据 - 是时候把它们放在一起了。


我选择了一种非常简单的方法将它们固定在一起 - 热胶。由于零件本身已经几乎适合(纳米适合外圈尺寸,nRF24 模块适合内圈尺寸和电池,虽然不适合任何零件,但不知何故并没有太大影响 - 不确定它是如何工作的,但我只是粘上了它在那里,不知何故真的没问题:)然后我将它缝到手头的一些随机腕带上(焊台包装的剩余物,用于焊接时接地的腕带),就是这样!

5. 测试
为了测试,我跑了一次,它运行得很好,除了一个惊喜。我使用了这样的设置,即在 192 BPM 时所有 LED 都亮起,因为根据所有建议,这样的心率对于我的参数来说太高了。令人惊讶的是,我在短短几分钟内就超过了它,甚至没有注意到这一点。我什至认为这可能是传感器错误,但不是 - 当我停下来时,它并没有立即下降,而是缓慢放松(当没有太多运动时,传感器是 100% 可靠的)。所以事实证明,有一段时间我的训练远高于我的健康阈值(至少对于我这个年龄/体重的标准成年人来说应该是健康的)。有趣的是:我从小就很喜欢(业余)运动,但我十几岁时就有心脏问题,而且随着时间的推移它们似乎消失了。但我从经验中知道,任何比快速步行更高的负荷对我来说都非常困难,但我一直在训练——随着时间的推移,这增加了我的极限,以至于现在我认为自己非常适合。现在我有一个问题 - 我的 BPM 是否只是由于青春期的心脏问题而高于正常水平,或者我真的在没有意识到这一点的情况下用力过猛?无论如何,我必须对它做些什么 - 要么增加监视器上的最大 BPM,要么减少训练强度。 :)
附言令人惊讶的是,uECG 作为 EMG 传感器的表现非常好——你可以在我的机器人手控制项目中阅读它
代码
- bpm_watch.ino
bpm_watch.inoArduino
#include#ifdef __AVR__ #include #endif#include #include #include #include int rf_cen =9; //nRF24 芯片使能 pinint rf_cs =8; //nRF24 CS pinRF24 rf(rf_cen, rf_cs);//管道地址-硬编码在uECG sideuint8_t pipe_rx[8] ={0x0E, 0xE6, 0x0D, 0xA7, 0, 0, 0, 0};// Arduino 连接到 NeoPixels?#define PIN 5// 有多少 NeoPixels 连接到 Arduino?#define NUMPIXELS 16Adafruit_NeoPixel pixel =Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);uint8_t swapbits (uint8_ECG)地址使用交换位顺序 // 反转单个字节中的位顺序 uint8_t v =0; if(a &0x80) v |=0x01; if(a &0x40) v |=0x02; if(a &0x20) v |=0x04; if(a &0x10) v |=0x08; if(a &0x08) v |=0x10; if(a &0x04) v |=0x20; if(a &0x02) v |=0x40; if(a &0x01) v |=0x80;返回 v;}void setup() { pixel.begin(); // 这将初始化 NeoPixel 库。 for(int i=0;i 0) //如果是 - 将其存储为新收到的 BPM rf_bpm =bb;长毫秒 =毫秒(); if(ms - last_pix_upd> 10) //不要太频繁地更新像素{ int r, g, b; last_pix_upd =毫秒; int bpm =rf_bpm; int max_bright =160; //最大亮度的值,最大值为 255。但您并不总是希望它达到最大值 :) float dd =25; //色调之间的BPM变化(蓝色->绿色->黄色->粉色->红色) float t1 =90, t2, t3, t4; //t1 - "base" BPM, 低于 t1 将是蓝色 t2 =t1 + dd; t3 =t2 + dd; t4 =t3 + dd; //改变颜色的代码取决于我们现在所处的 t1...t4 范围 if(bpm
示意图
没有文件。
nrf24_led_ring_o2Gij5oigT.fzz制造工艺