ワイヤレスECG心拍モニター:Arduino NanoとnRF24で作るウェアラブル設計
部品と材料
| uECGデバイス | ×1 | 実際のBPM測定用。nRF24互換プロトコルでデータ送信可能 |
| Arduino Nano R3 | ×1 | |
| nRF24モジュール(汎用) | ×1 | uECGからBPMデータを受信するために必要。どのモジュールでも動作 |
| Adafruit NeoPixelリング:WS2812 5050 RGB LED | ×1 | |
| LiPoバッテリー | ×1 | 200mAh以上の容量のもの |
必要な工具と機械
- はんだごて(汎用)
プロジェクト概要
これは私の心拍モニタリングプロジェクトの第2弾です。前回は胸に心拍を表示し、uECGと有線接続していました。見た目はクールでしたが実用的ではありません - 正確なLED数が見えにくい、視野外にある、uECGへの接続ワイヤーがセンサーに問題を引き起こし、走ると基本的に動作しません。
このバージョンはそれらすべてを解決:腕時計型で走行中でも視界内にあり、ワイヤレスなので測定値に歪みがなく、ランニング中でも正常に動作し、心臓負荷を追跡できます。
1. コンポーネント
前回と同様、すべてのハードワークはuECGが担当 - データ測定とBPMのオンボード計算。さらに、ダイレクトリンクモードに切り替えると、高解像度ECGデータ(ここでは未使用)とともに、汎用nRF24チップと互換性のある無線プロトコルですべての情報を送信。第2の重要コンポーネントはnRF24モジュール。Arduino Nanoは小さなLEDリングの下にぴったり収まるサイズなのでコントローラーとして使用(実際は何でも動作)。
2. 回路図
nRF24モジュールの接続はシンプルではありません。SPI全線(MISO、MOSI、SCK、CS)、チップイネーブル線、電源を接続する必要があります。コンパクトにまとめるならピンヘッダーをすべて外し、パッドに直接ワイヤーをはんだ付け。nRFだけで7本のワイヤー、14箇所のはんだ付けが必要。良いニュースは他はシンプル:LEDリングはデータ線1本と電源線2本、バッテリーコネクターに電源線2本。
接続一覧:
- nRF24ピン1 (GND) → Arduino GND
- nRF24ピン2 (Vcc) → Arduino 3.3V
- nRF24ピン3 (Chip Enable) → Arduino D9
- nRF24ピン4 (SPI:CS) → Arduino D8
- nRF24ピン5 (SPI:SCK) → Arduino D13
- nRF24ピン6 (SPI:MOSI) → Arduino D11
- nRF24ピン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. プログラム
ソフトウェアで複雑なのはRFチャネル設定のみ。動作させるまでにかなり時間を費やしましたが、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ピン
RF24 rf(rf_cen, rf_cs);
// パイプアドレス - uECG側でハードコード
uint8_t pipe_rx[8] = {0x0E, 0xE6, 0x0D, 0xA7, 0, 0, 0, 0};
#define PIN 5 // NeoPixels接続ピン
#define NUMPIXELS 16 // NeoPixelsの数
Adafruit_NeoPixel pixels = 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();
for(int i=0;i 0) 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 - 「基準」BPM、これ未満は青
t2 = t1 + dd; t3 = t2 + dd; t4 = t3 + dd;
// どのt1...t4範囲にいるかによる色変更
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; // ランニング用なので80BPM未満は非表示、高負荷域で高感度に
for(int i=0;i
4. リストバンド組み立て
すべての配線をはんだ付けし、プログラムを書き込み、uECGデータが受信できることを確認したら、すべてをまとめます。
非常にシンプルな方法でまとめました - ホットグルー。パーツ自体がほぼフィットしています(Nanoは外側リングサイズ、nRF24モジュールは内側リングサイズ、バッテリーはどのパーツにもフィットしないが、なぜか邪魔にならない - どうなってるのか不明だが、ただ貼り付けたらうまくいった)。その後、手元にあった適当なリストバンド(はんだ付けステーション付属の余り、はんだ付け時のグラウンディング用バンド)に縫い付けて完成!
5. テスト
テストでランニングに行き、完璧に動作しましたが、一つ驚きがありました。192BPMで全LED点灯になる設定にしていましたが(推奨値を超えるため)、数分のランニングで簡単に超えてしまいました。センサーエラーかと思いましたが、止めても即座に下がらず、ゆっくり緩やかに下がっていきました(動きが少ない時はセンサーは100%信頼性があります)。つまり、標準的な成人の健康的な閾値(年齢/体重ベース)をかなり超えてトレーニングしていたことになります。
面白いことに:子供の頃からアマチュアスポーツをやっていますが、10代で心臓に問題があり、時間とともに治ったと思っていました。しかし経験では、早歩き以上の負荷は常にきつかったのに、トレーニングを続けて限界が上がり、今では自分をかなりフィットだと思っています。さて質問です - 思春期の心臓問題のせいでBPMが生まれつき高いのか、気づかないうちに追い込みすぎているのか? どちらにしろ対策が必要です - モニターの最大BPMを上げるか、トレーニング強度を下げるか。:)
追記:驚いたことにuECGはEMGセンサーとしても非常に優秀 - 詳細は私のロボットハンド制御プロジェクトを参照
コード
bpm_watch.ino
#include#ifdef __AVR__ #include #endif #include #include #include #include int rf_cen = 9; int rf_cs = 8; RF24 rf(rf_cen, rf_cs); 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 0) 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; float dd = 25; float t1 = 90, t2 = t1+dd, t3 = t2+dd, t4 = t3+dd; 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 回路図
ドキュメントなし。nrf24_led_ring_o2Gij5oigT.fzz
製造プロセス
- Raspberry Piで構築する空気質・気象ステーション:InfluxDB & Grafana活用
- Raspberry PiとPythonでリモート温度センサーを構築 – ステップバイステップガイド
- Raspberry Piダッシュボードで家庭の温度を監視する
- リモート心拍数監視システム:AWS IoTアラート付き
- Arduinoで作るモニター用Ambilightシステム:没入感アップの自作ガイド
- Arduinoとスマホで作るスマート電圧計 – 簡単DIYプロジェクト
- ArduinoとMAX30100で作るリアルタイムIoT心拍数モニター
- フォトレジスタとArduinoで作る高精度心拍数モニター
- CNC加工:材料とコンポーネントの精密加工
- ルネサスとAltran、ソーシャルディスタンス用初の低レートパルスUWB腕時計を開発
- スマート衣類に統合された心拍センサーで信頼性の高いウェアラブル監視を実現