使用心电图的心跳指示器
组件和用品
| × | 1 | ||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 1 |
必要的工具和机器
![]() |
|
关于这个项目
多年来,我只是想用 LED 做一些能随着我的心跳而闪烁的东西(不仅在我完全静止的时候,而且还在这里和那里跳过一个节拍)。结果出乎意料地困难,我尝试了多年,但都失败了。但现在不行了!
实际上,所有繁重的工作都是由 uECG 完成的——一个小型的可穿戴式心电图设备,它是开源的,并且有一个 Arduino 友好的输出引脚(该引脚随着每次心跳变高/变低)。处理这些引脚状态比处理 ECG 信号要容易得多,我已经尝试充分利用它。
UPD:您可能想检查这个项目的第二次迭代,它通过无线电链接接收数据。
1.原理图
由于我们这里只处理数字信号,所以非常简单。但作为可穿戴设备,如果大多数连接都是焊接的,它会更可靠(而且更小)——为了快速测试,它没有必要,但如果你要在一些繁重的活动中佩戴它,我强烈建议你这样做。
原理图如下所示:

- LED 环的 DI 引脚连接到引脚 D11(可在代码中配置)
- uECG 设备的 DRV 引脚转到引脚 D3(也可配置)
- 电池的 + 连接到 Arduino 5V 和 LED 环形 5V 输入
- 电池 - 连接到 Arduino GND、环形 GND 和 uECG 的 GND
我直接使用 LiPo 电池作为 5V 输入 - 没有错误,如果你将它连接到 Vin - 它不会可靠地工作(Vin 上的电压调节器会引入电压降,我们在这里绝对买不起)。问题是,只要输入电压不低于 3.4 伏,Arduino 就是稳定的。锂聚合物电池在充满电时的电压为 4.2 伏,只有在剩余电量低于 15% 时才会达到 3.4 伏。因此,使用任何大于 ~200 mAh 的电池,您都可以获得不错的运行时间。除此之外,请记住电池应该通过一些连接器连接:) 因为你想把它从原理图上断开并偶尔充电。
2.代码
该程序以简单的方式工作:它不断读取 D3 引脚,并在检测到变化时 - 将该变化的时间推送到 20 个元素的数组中。第一个和最后一个元素之间的差异除以 20,就是每节拍的平均时间(以毫秒为单位)。因此,将 1 分钟(60000 毫秒)除以该数字即可得出 BPM 值。您可以调整数组中的元素数量。较少数量的元素会导致更快的响应,但不太稳定的结果(节拍检测中的任何问题都会导致计算的 BPM 出现大幅跳跃)。元素个数越多,数据越稳定,但BPM变化快时响应速度越慢。
然后 BPM 被映射成颜色(当 BPM 从低到高时,蓝色->绿色->黄色->粉红色->红色),并映射到 LED 数量:80 BPM 八段亮,110 - 11 段等等(代码中也可以调整比例)。
#include
#ifdef __AVR__
#include
#endif
// LED 环的 DI 引脚
#define PIN 11
// 环中的像素数
#define NUMPIXELS 16
// 用于连接 uECG 的输入引脚
int in_pin =3;
Adafruit_NeoPixel 像素 =Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
void setup() {
pixels.begin(); // 这将初始化 NeoPixel 库。
pinMode(in_pin, INPUT); //设置引脚为输入模式
digitalWrite(in_pin, 1); //启用PULLUP:这很关键,uECG没有内部上拉
}
//我们存储最后20个心跳以平均BPM
//具有更高的值,它会变得更可靠,
//但是当 BPM 变化时,需要更多时间才能看到输出变化
#define BEAT_HIST 20
long beats[BEAT_HIST];
void push_beat(long ms) //移动数组中的所有节拍并插入当前一个
{
for(int x =0; x {
beats[x] =beats[x+1];
}
beats[BEAT_HIST-1] =ms;
}
int get_bpm() //使用第一个和最后一个节拍之间的时间差
{
long dt =beats[BEAT_HIST-1] - beats[0];
long bpm =BEAT_HIST * 60000 / dt;
return bpm;
}
long last_pix_upd =0; //跟踪我们上次更新像素的时间
int prev_in_state =0; //输入引脚的先前状态:我们只想处理状态的变化
void loop()
{
long ms =millis();
int in_state =digitalRead(in_pin ); //1 当未检测到节拍时,节拍为 0
if(in_state ==1 &&prev_in_state ==0) //仅对变化做出反应
{
push_beat(ms);
}
prev_in_state =in_state;
if(ms - last_pix_upd> 10) //不要经常更新像素
{
int r, g, b;
last_pix_upd =ms;
int bpm =get_bpm();
int max_bright =120; //最大亮度的值,最大值为 255。但您并不总是希望它达到最大值 :)
float dd =20; //不同色调之间的 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; }
if(in_state) //当不在节拍时,1/4强度,所以只突出显示节拍
{
r *=0.25;
g *=0.25;
b *=0.25;
}
int on_pixels =(bpm+5)/10; //使用的 LED 数量:对于 60 BPM,6 个 LED 将亮起,对于 120 - 12 等
for(int i=0;i {
if( i else pixel.setPixelColor(i, pixel.Color(0,0,0)); //关闭所有其他LED
}
pixels.show();
}
}
3.作为可穿戴设备组装
将 Arduino 放入环内很方便 - 它几乎完美匹配尺寸。电池也适合附近。不要忘记 uECG 是放在胸前的——所以你需要一根带连接器的电线,首先你把它放好,然后你穿上带有其他组件的衬衫,然后插入连接器。否则戴上真的很不方便——相信我,我试过))



基本上就是这样 - 如果一切正常,在您插入所有连接器后 30 秒内,它将开始闪烁并指示 BPM。
4.现场测试
我在步行和跑步时对其进行了测试 - 发现在跑步过程中,电池会在 ECG 传感器上方反弹,从而导致读数失真。当我稍微移动它时,结果发现连接 uECG 和 Arduino 的电线太短,每一步都会拉动 ECG 传感器,再次扭曲读数。总的来说,我只有在走路和站立时才能得到可靠的节拍,而不是跑步——但我想我会改进这一点。传感器本身,当我将它与不同的衬衫搭配使用时,在跑步过程中也能正确显示 BPM(通过其应用程序检查)。
此外,事实证明,胸部上的 LED 可能看起来很酷,但实际上毫无用处。低头看脉搏真的很不方便。我想在下一次迭代中,我会制作某种可以指示节拍的腕带。
附言如果你对 uECG 项目感兴趣 - 你可以查看它的 hackaday 页面,有很多技术细节、PCB 设计、讨论和项目日志
代码
- uECG_pixel_ring.ino
uECG_pixel_ring.inoArduino
#include#ifdef __AVR__ #include #endif// LED 环的 DI 引脚#define PIN 11// 环中的像素数#define NUMPIXELS 16// 输入用于连接 uECGint in_pin =3 的引脚; Adafruit_NeoPixel 像素 =Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);void setup() { pixel.begin(); // 这将初始化 NeoPixel 库。 pinMode(in_pin,输入); //将引脚设置为输入模式 digitalWrite(in_pin, 1); //启用PULLUP:这很关键,uECG没有内部上拉}//我们存储最后20个心跳来平均它们的BPM//值越高,它会变得更可靠,//但需要更多BPM 变化时观察输出变化的时间#define BEAT_HIST 20long beats[BEAT_HIST];void push_beat(long ms) //移动数组中的所有节拍并插入当前节拍{ for(int x =0; x 10) //不要太频繁地更新像素{ int r, g, b; last_pix_upd =毫秒; int bpm =get_bpm(); int max_bright =120; //最大亮度的值,最大值为 255。但您并不总是希望它达到最大值 :) float dd =20; //色调之间的BPM变化(蓝色->绿色->黄色->粉色->红色) float t1 =90, t2, t3, t4; //t1 - "base" BPM, 低于 t1 将是蓝色 t2 =t1 + dd; t3 =t2 + dd; t4 =t3 + dd; //改变颜色的代码取决于我们现在所处的 t1...t4 范围 if(bpm
示意图

制造工艺