Беспроводной ECG-монитор пульса: носимое устройство на Arduino Nano и nRF24
Компоненты и расходные материалы
| Компонент | Количество |
|---|---|
| uECG-устройство (измеряет BPM, передаёт по nRF24-совместимому протоколу) | 1 |
| Arduino Nano R3 | 1 |
| Модуль nRF24L01 (любой) | 1 |
| Кольцо NeoPixel WS2812 5050 RGB (16 светодиодов) | 1 |
| LiPo аккумулятор 200+ мАч | 1 |
Инструменты
- Паяльник
О проекте
Вторая версия проекта мониторинга сердца. Первая была проводной, на груди — неудобно для бега. Новая версия: на запястье, беспроводная, работает при движении.
1. Компоненты
uECG делает всю сложную работу: измеряет ЭКГ, считает BPM. В режиме «direct link» отправляет пакеты (BPM + сырой ЭКГ) по протоколу, совместимому с nRF24. Arduino Nano получает пакеты, фильтрует, визуализирует на кольце NeoPixel.
2. Схема подключения
nRF24 требует 7 проводов (SPI + CE + CSN + питание). Пинхэдеры лучше снять, паять прямо к пятам для компактности.
| nRF24 пин | Arduino Nano |
|---|---|
| 1 GND | GND |
| 2 VCC | 3.3V |
| 3 CE | D9 |
| 4 CSN | D8 |
| 5 SCK | D13 |
| 6 MOSI | D11 |
| 7 MISO | D12 |
NeoPixel: VCC→5V, GND→GND, DI→D5. Батарея: + → 5V, - → GND (через разъём для зарядки).
Важно: SPI (MOSI/MISO/SCK) жестко завязан на D11/D12/D13. Остальные пины можно переназначить в коде.
3. Программа
Сложность — настройка RF-канала. uECG и nRF24 используют разный порядок бит в адресе pipe — нужна функция swapbits(). CRC отключён для совместимости, поэтому вводится фильтр: берём BPM только если 5 последних пакетов дали одно значение (uECG шлёт ~100 пакетов/сек — задержки нет).
Визуализация: BPM → цвет (синий→зелёный→жёлтый→розовый→красный) и число зажжённых пикселей. База 80 BPM, шаг 25 BPM. Ниже 80 — всё погасено (фокус на нагрузке).
Исходный код (bpm_watch.ino)
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#include <SPI.h>
#include <RF24.h>
#include <RF24_config.h>
#include <nRF24L01.h>
int rf_cen = 9;
int rf_cs = 8;
RF24 rf(rf_cen, rf_cs);
// Адрес pipe (hardcoded в uECG), биты перевёрнуты
uint8_t pipe_rx[8] = {0x0E, 0xE6, 0x0D, 0xA7, 0, 0, 0, 0};
#define PIN 5
#define NUMPIXELS 16
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
uint8_t swapbits(uint8_t a) {
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();
for (int i = 0; i < NUMPIXELS; i++) pixels.setPixelColor(i, pixels.Color(1,1,1));
pixels.show();
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
for (int x = 0; x < 8; x++) 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 должен быть в raw data mode (длинное нажатие кнопки)
}
long last_pix_upd = 0;
byte in_pack[32];
int rf_bpm = 0;
int bpm_hist[5];
void loop() {
if (rf.available()) {
rf.read(in_pack, 32);
int bb = in_pack[5]; // BPM в 5-м байте
// Фильтр: сдвиг истории
for (int n = 0; n < 4; n++) bpm_hist[n] = bpm_hist[n+1];
bpm_hist[4] = bb;
for (int n = 0; n < 5; n++) if (bpm_hist[n] != bb) bb = -1;
if (bb > 0) rf_bpm = bb;
}
long ms = millis();
if (ms - last_pix_upd > 10) {
last_pix_upd = ms;
int bpm = rf_bpm;
int max_bright = 160;
float dd = 25;
float t1 = 90, t2 = t1+dd, t3 = t2+dd, t4 = t3+dd;
int r, g, b;
if (bpm < t1) { r=0; g=0; b=max_bright; }
else if (bpm < t2) { r=0; g=max_bright*(bpm-t1)/dd; b=max_bright-g; }
else if (bpm < t3) { r=max_bright*(bpm-t2)/dd; g=max_bright-r; b=r/4; }
else if (bpm < t4) { r=max_bright; g=0; b=max_bright/2 - max_bright*(bpm-t3)/(2*dd); }
else { r=max_bright; g=0; b=0; }
int on_pixels = (bpm - 80) / 8;
for (int i = 0; i < NUMPIXELS; i++) {
if (i < on_pixels) pixels.setPixelColor(NUMPIXELS-i-1, pixels.Color(r,g,b));
else pixels.setPixelColor(NUMPIXELS-i-1, pixels.Color(0,0,0));
}
pixels.show();
}
}
4. Сборка браслета
Все детали клеятся термоклеем: Nano по внешнему диаметру кольца, nRF24 внутрь, батарея — где влезет. Пришито к ремешку от антистатического браслета.
5. Тестирование
Автор пробежал — работает. Удивило: пульс достиг 192 BPM (все 16 LED загорелись) через пару минут бега. Датчик надёжен (при остановке пульс плавно снижался). Вопрос: так ли это из-за перенесённых проблем с сердцем в юности, или нагрузка слишком высока? Понадобится либо поднять порог, либо снизить интенсивность.
P.S. uECG хорошо работает и как ЭМГ — см. проект «Robotic Hand Control».
Схемы
- Файл для Fritzing:
nrf24_led_ring_o2Gij5oigT.fzz
Производственный процесс
- Renesas и Altran выпускают первые UWB-наручные часы с низкоимпульсной архитектурой для социального дистанцирования
- Сборка станции качества воздуха и погоды на Raspberry Pi с InfluxDB и Grafana
- Создание удалённого датчика температуры на Raspberry Pi и Python — пошаговое руководство
- Мониторинг домашней температуры с панелью управления на Raspberry Pi
- Система удалённого мониторинга сердечного ритма с алертами AWS IoT
- Создание потрясающей системы Ambilight для монитора на Arduino
- Умный вольтметр на Arduino и смартфоне — простое DIY-проект
- IoT-монитор пульса в реальном времени на Arduino и MAX30100
- Интегрированные датчики сердечного ритма в умной одежде: надежный носимый мониторинг
- CNC-обработка: прецизионное изготовление материалов и компонентов