工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Manufacturing Technology >> 製造プロセス

リアルタイム心拍LEDビジュアライザー – ECGパワード

部品と材料

部品 数量 備考
uECGデバイス 1 オープンソース着用型ECGデバイス
Arduino Nano R3 1
Adafruit NeoPixelリング: WS2812 5050 RGB LED 1 16ピクセル
リチウムイオンバッテリー 1000mAh 1

必要な工具と機器

プロジェクト概要

長年、心拍に合わせて光るLED作品を作りたいと思っていました(じっとしているときだけでなく、動いているときも、たまに拍動をスキップするような本物の心拍で)。これが驚くほど難しく、何年も試行錯誤して失敗し続けました。でももう違います!

実は重い処理はすべてuECGがやってくれます。これは小型の着用型ECGデバイスで、オープンソースかつArduinoフレンドリーな出力ピン(心拍ごとにHigh/Lowが切り替わる)を備えています。このピン状態を処理するのは、生のECG信号を処理するより遥かに簡単で、これを最大限活用しました。
更新: 第2弾プロジェクト(無線リンクでデータ受信版)もチェックしてみてください。

1. 回路図

デジタル信号のみを扱うため非常にシンプルです。しかし着用型として使うなら、ほとんどの接続をはんだ付けしたほうが信頼性が高く(小型化も可能)。クイックテストなら不要ですが、激しい運動中に着用するなら強く推奨します。

リアルタイム心拍LEDビジュアライザー – ECGパワード

LiPoバッテリーを直接5V入力に接続しています。間違いではありません。Vinに接続すると電圧レギュレーターのドロップで安定動作しません。Arduinoは入力電圧が3.4V以上なら安定動作します。LiPoは満充電4.2Vから始まり、3.4Vになるのは残量15%未満のとき。したがって〜200mAh以上のバッテリーなら十分な動作時間が得られます。バッテリーはコネクター経由で接続してください(充電のために着脱する必要があるため)。

2. コード

プログラムはシンプルに動作します:D3ピンを常時読み取り、変化を検出したらそのタイムスタンプを20要素の配列に追加。最初と最後の要素の時間差を20で割ると平均拍動間隔(ミリ秒)が求まり、60000(1分)をそれで割るとBPM値になります。配列要素数は調整可能。少なければ応答が速いが不安定、多ければ安定だが急激なBPM変化への追従が遅くなります。

BPMを色(青→緑→黄→ピンク→赤、低→高)と点灯LED数(80BPMで8個、110BPMで11個など、スケール調整可)にマッピングします。

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
// LEDリングのDIピン
#define PIN 11
// リングのピクセル数
#define NUMPIXELS 16
// uECG接続用入力ピン
int in_pin = 3;

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();
  pinMode(in_pin, INPUT);
  digitalWrite(in_pin, 1); // プルアップ有効化:重要!uECGに内部プルアップなし
}

// 直近20拍を保存してBPMを平均化
// 値を大きくすると信頼性向上だが、BPM変化への反応が遅くなる
#define BEAT_HIST 20
long beats[BEAT_HIST];

void push_beat(long ms) {
  for (int x = 0; x < BEAT_HIST-1; 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: 基準BPM、これ未満は青
    t2 = t1 + dd;
    t3 = t2 + dd;
    t4 = t3 + dd;
    
    // BPMに応じた色計算
    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; }
    
    // 拍動間は1/4輝度にして拍動瞬間を強調
    if (in_state) {
      r *= 0.25; g *= 0.25; b *= 0.25;
    }
    
    int on_pixels = (bpm + 5) / 10; // 点灯LED数: 60BPMで6個, 120BPMで12個など
    for (int i = 0; i < NUMPIXELS; i++) {
      if (i < on_pixels) pixels.setPixelColor(i, pixels.Color(r, g, b));
      else pixels.setPixelColor(i, pixels.Color(0, 0, 0));
    }
    pixels.show();
  }
}

3. 着用型としての組み立て

Arduinoをリング内部に収めるとサイズがほぼぴったり。バッテリーも近くに配置可能。uECGは胸に装着するため、コネクター付きワイヤーで接続します。先にuECGを装着し、その上から他コンポーネント内蔵のシャツを着て、最後にコネクターを接続する順序が重要です。逆だと着用が非常に面倒です(経験済み)。

リアルタイム心拍LEDビジュアライザー – ECGパワード
リアルタイム心拍LEDビジュアライザー – ECGパワード
リアルタイム心拍LEDビジュアライザー – ECGパワード

すべて正しく接続すれば、コネクター接続後30秒以内に点灯し、BPMを表示開始します。

4. 実地テスト

歩行とランニングでテスト。ランニング中、バッテリーがECGセンサーの上で跳ねて読み取りを歪ませることが判明。位置をずらすと、今度はuECG-Arduino接続ワイヤーが短すぎて、歩行のたびにECGセンサーを引っ張り、また歪みが発生。全体として、歩行と静止時は確実に拍動検出できましたが、ランニング時はまだ不安定です(改善予定)。別のシャツでセンサー単体テストしたときは、ランニング中もアプリで正しいBPMが表示されていました。

また、胸のLEDは見た目はカッコいいですが、実用性は低いです。自分の脈拍を確認するために下を向くのは不便。次期版ではリストバンド型で拍動表示する予定です。

P.S. uECGプロジェクトに興味があれば、Hackadayページに技術詳細、PCB設計、議論、プロジェクトログが豊富にあります。

コード

uECG_pixel_ring.ino (Arduino)

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 11
#define NUMPIXELS 16
int in_pin = 3;
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();
  pinMode(in_pin, INPUT);
  digitalWrite(in_pin, 1);
}

#define BEAT_HIST 20
long beats[BEAT_HIST];

void push_beat(long ms) {
  for (int x = 0; x < BEAT_HIST-1; 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);
  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;
    float dd = 20;
    float t1 = 90, t2, t3, t4;
    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; }
    if (in_state) {
      r *= 0.25; g *= 0.25; b *= 0.25;
    }
    int on_pixels = (bpm + 5) / 10;
    for (int i = 0; i < NUMPIXELS; i++) {
      if (i < on_pixels) pixels.setPixelColor(i, pixels.Color(r, g, b));
      else pixels.setPixelColor(i, pixels.Color(0, 0, 0));
    }
    pixels.show();
  }
}

回路図

リアルタイム心拍LEDビジュアライザー – ECGパワード

製造プロセス

  1. 体外式除細動器:歴史、設計、製造
  2. ローラーピン:エトルリア起源から現代の職人技へ
  3. 人工心臓:技術、歴史、将来展望
  4. 人工心臓弁:種類、歴史、未来の革新
  5. 現代ボウリングピンの進化と職人技
  6. Raspberry PiでPIRモーションセンサーとLEDを制御
  7. Raspberry Piダッシュボードで家庭の温度を監視する
  8. リモート心拍数監視システム:AWS IoTアラート付き
  9. ナックルジョイント完全解説:設計・用途・主要特徴を図解で理解
  10. スマートスピーカーで非接触心拍モニタリング─ 不整脈検知まで実現する革新システム
  11. グラフェン電子タトゥーで長期間連続・高精度心臓監視、心電図と心震図を同時取得