SSブログ

SPRESENSEのマイク入力をADCにして心拍センサーを取り込む! [SPRESENSE]

前回、SPRESENSEのADCで心拍データをキャプチャしましたが、DC取り込みなので体動で大きくレベルが変化していました。そこで、今回はSPRESENSEのマイク入力に心拍センサーを接続してデータを取り込んでみました。マイク入力はAC入力なので電気的にDC成分をカットできます。





SPRESENSEチップのデータシートを見るとマイク入力のレンジは0.9V(±0.45V)なので、ADCの0.7Vよりレンジが広く優秀です。


MICA.png


試しに、モノラル16k/16bitをSDカードに録音?してみたのですが、とにかくデータが取れすぎる。心拍センサーのノイズを拾いまくりです。


HeartBeatRec.png


なので、冒頭の動画は1024サンプルの平均をとってさらに平均をとったデータの128サンプルで平均をとるという贅沢すぎるデータ処理をしています。


#include <Audio.h>

AudioClass *theAudio;

void setup() {
  int ret;

  Serial.begin(115200);
  Serial.println("Init Audio Library");
  theAudio = AudioClass::getInstance();
  theAudio->begin();

  Serial.println("Init Audio Recorder");
  /* Select input device as AMIC */
  theAudio->setRecorderMode(AS_SETRECDR_STS_INPUTDEVICE_MIC);

  theAudio->initRecorder(AS_CODECTYPE_PCM, "/mnt/spif/BIN", AS_SAMPLINGRATE_16000, AS_CHANNEL_MONO);
  
  Serial.println("Capturing start!");
  theAudio->startRecorder();

}

#define AVE_WINDOW 128
int16_t average[AVE_WINDOW];

static int num = 0;
void loop()
{
  static const int32_t buffer_sample = 1024;
  static const int32_t buffer_size = buffer_sample * sizeof(int16_t);
  static char  frame[buffer_size];
  uint32_t read_size;

  int err = theAudio->readFrames(frame, buffer_size, &read_size);
  if (err != AUDIOLIB_ECODE_OK && err != AUDIOLIB_ECODE_INSUFFICIENT_BUFFER_AREA) {
    printf("Error err = %d\n", err);
    sleep(1);
    theAudio->stopRecorder();
    exit(1);
  }

  int16_t *buf = (int16_t*)frame;
  int32_t sum = 0;
  for (int i = 0; i < buffer_sample; ++i) {
    sum += buf[i];
  }
  
  average[num] = sum / buffer_sample;
  if (++num >= AVE_WINDOW) num = 0;
  
  if (num == 0) {
    sum = 0;
    for (int i = 0; i < AVE_WINDOW; ++i) {
      sum += average[i];
    }
    int16_t output = sum / AVE_WINDOW;
    Serial.println(output);
  } 
}



マイク入力はゲインも設定できるので、AC限定ですがADCとしては非常に優秀です。皆さんもつかってみてはいかがでしょう?







SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

  • 出版社/メーカー: スプレッセンス(Spresense)
  • メディア: Tools & Hardware



SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

  • 出版社/メーカー: スプレッセンス(Spresense)
  • メディア: Tools & Hardware




nice!(30)  コメント(0) 
共通テーマ:趣味・カルチャー

SPRESENSE で心拍センサーを動かしてみた! [SPRESENSE]

SPRESENSEメインボードで "Pulse Sensor" で有名なアナログ心拍センサーを動かしてみました。アナログの心拍センサーはお安いのに加え、心拍計のように波形もとれるので個人的には好きです。


sDSC_1320.jpg


SPRESENSEメインボードで心拍センサーを動かすことの注意点をいくつかまとめてみました。

■ 心拍センサーの出力3.3V出力を分圧して入力
心拍センサーは3.3V電源で動作しますが、SPRESENSEメインボードのADCは0.7Vなので心拍センサーの信号出力を分圧する必要があります。今回、分圧抵抗は省電力にするため330kΩと100kΩを使いました。


■ 割り込み関数内では ”analogRead” が使えない
最初、Pulse Sensor のサンプルプラグラム同様に、割り込み関数内で心拍数カウントをするプログラムを作りましたが、なんと analogRead が割り込み関数内で使うことができないことが発覚。チュートリアルを見ると SPRESENSE は割り込み内で使える関数に制限があるようなので要注意です。


■ 体動により大きく値が変動する
この方式は体動により値が大きく変動するので、それをキャンセル必要があります。心拍データに対して変動は非常に大きいので平均値をとって変動分を差し引く必要があります。





平均の算出は比較的大きなメモリが必要になります。SPRESENSEののマイク入力のA/DはAC入力でDC成分は排除できるので、心拍センサーを取り込むにはマイク入力のほうがいいかも知れません。


■ 心拍の心拍ピーク(P)と心拍基準値(T)の設定
プログラム内で心拍のピーク(P)と心拍の基準値(T)は、心拍の波形を確認してから値を決めてください。しきい値はP値とT値の半分を設定すれば安定して心拍を検出してくれるようです。
https://pulsesensor.com/pages/pulsesensor-playground-toolbox




■ SPRESENSEのLEDを心拍表示に使う
SPRESENSEのLEDは"analogWrite"に対応していないようです。ですので4つのLEDを使って表示を工夫しました。また、割り込み内で処理できなかったので、LED処理は task_create で別スレッドにしました。


■ SPRESENSE の Pulse Sensor 読み取りスケッチ(暫定版)
以上の点を踏まえて作成したスケッチです。

#define N (10)
#define INTERVAL (2000)  // 2 msec

int BPM;
int Signal;
int IBI = 600;
boolean Pulse = false;
boolean QS = false;

int Rate[N];
unsigned long CurrBeatTime = 0;
unsigned long LastBeatTime = 0;
int P = 570;
int T = 510;
int Threshold = 540;
int Amplifier = 100;

static bool bInterval = false;
int PulseSensorPin = 2;

#define AVE_WINDOW 64
#define SIG_WINDOW 4
int Average[AVE_WINDOW];
int Signals[SIG_WINDOW];
static int ave_num = 0;
static int sig_num = 0;
void pulse_check(void) {

  CurrBeatTime = millis(); // msec
  unsigned long interval = CurrBeatTime - LastBeatTime;

  Signal = analogRead(PulseSensorPin);
  Signals[sig_num]  = Signal;
  Average[ave_num]  = Signal;
  int mean_val = 0;
  for (int i = 0; i < AVE_WINDOW; ++i) {
    mean_val += Average[i];
  }
  mean_val /= AVE_WINDOW;
  if (++ave_num >= AVE_WINDOW) ave_num = 0;
  
  int sum = 0;
  for (int i = 0; i < SIG_WINDOW; ++i) {
    sum += Signals[i];
  }
  Signal = sum/SIG_WINDOW - mean_val + 512;
  if (++sig_num >= SIG_WINDOW) sig_num = 0;
  
  Serial.println(Signal);
  
  // hold bottom
  if ((Signal < Threshold) && (interval > (IBI*3) / 5)) {
    if (Signal < T) T = Signal;
  }
   
  // hold peak
  if (Signal > Threshold && Signal > P) {
    P = Signal;
  }
  
  // calculate BPM
  if (interval > 250 /* ms */) {
    
    if ((Signal > Threshold) && !Pulse && (interval > (IBI*3) / 5)) {
      Pulse = true;
      IBI = interval;
      LastBeatTime = CurrBeatTime;
            
      if (Rate[0] < 0) { // first time
        Rate[0] = 0;
        return;
      } else if (Rate[0] == 0) {  // second time
        for (int i = 0; i < N; ++i) Rate[i] = IBI;
      }
      
      uint16_t running_total = 0;     
      for (int i = 0; i < N-1; ++i) {
        Rate[i] = Rate[i+1];
        running_total += Rate[i];
      }
      
      Rate[N-1] = IBI;
      running_total += IBI;
      running_total /= N;
      BPM = 60000 / running_total;
      QS = true;
    }
  }
  
  // check if Signal is under Threshold
  if ((Signal < Threshold) && Pulse) {
    Pulse = false;
    Amplifier = P - T;
    Threshold = Amplifier / 2 + T; // revise Threshold
    P = Threshold;
    T = Threshold;
  }
  
  // check if no Signal is over 2.5 sec
  if (interval > 2500 /* ms */) {
    Threshold = 515;
    P = 520;
    T = 510;
    LastBeatTime = CurrBeatTime;
    for (int i = 0; i < N; ++i) {
      Rate[i] = -1;
    }
  }

  return;
}

unsigned int interval_check() {
  bInterval = true;
  return INTERVAL;
}

void led_control() {
  static int led_pattern[5] = {0, 0x01, 0x03, 0x07, 0x0f};
  static int fade = 0;

  while (true) { 
    if (QS) {
      fade = 4;
      QS = false;
    }

    digitalWrite(LED0, (led_pattern[fade] & 0x08));
    digitalWrite(LED1, (led_pattern[fade] & 0x04));
    digitalWrite(LED2, (led_pattern[fade] & 0x02));
    digitalWrite(LED3, (led_pattern[fade] & 0x01));
    if (--fade < 0) fade = 0;
    usleep(100000);
  }
}

void setup() {
  Serial.begin(115200); 
  Serial.println("Start pulse sensor!");
  LastBeatTime = millis(); // msec
  attachTimerInterrupt(interval_check, INTERVAL);
  task_create("led control", 120, 1024, led_control, NULL);
  
}

void loop() {
  if (bInterval) {
    pulse_check();
    bInterval = false;
  }
  usleep(1000);
}



■ SPRESENSE で Pulse Sensor を動かしてみた

あまり綺麗なコードではありませんが、一応動きましたのでその様子を動画にしました。




そのうちマイク入力でも試してみたいと思います!
(^^)/~








Interface(インターフェース) 2015年 04 月号

Interface(インターフェース) 2015年 04 月号

  • 出版社/メーカー: CQ出版
  • 発売日: 2015/02/25
  • メディア: 雑誌




nice!(28)  コメント(0) 
共通テーマ:趣味・カルチャー

"Arduino Portenta H7" に "Vison Shield" が登場!! [Arduino]

SPRESENSE のコピー品かと思われるようなデザインの Arduino Portenta H7。25mm x 66mm で SPRESENSE よりも一回り大きいくらいのボードです。


ArduinoPortentaH7.png


今一度、仕様をおさらいしてみたいと思います。

Microcontroller STM32H747XI dual Cortex®-M7+M4 32bit low power ARM MCU (datasheet)
Radio module Murata 1DX dual WiFi 802.11b/g/n 65 Mbps and Bluetooth 5.1 BR/EDR/LE (datasheet)
Secure Element (default) NXP SE0502 (datasheet)
Board Power Supply (USB/VIN) 5V
Supported Battery Li-Po Single Cell, 3.7V, 700mAh Minimum (integrated charger)
Circuit Operating Voltage 3.3V
Current Consumption 2.95 μA in Standby mode (Backup SRAM OFF, RTC/LSE ON)
Display Connector MIPI DSI host & MIPI D-PHY to interface with low-pin count large display
GPU Chrom-ART graphical hardware Accelerator[トレードマーク]
Timers 22x timers and watchdogs
UART 4x ports (2 with flow control)
Ethernet PHY 10 / 100 Mbps (through expansion port only)
SD Card Interface for SD Card connector (through expansion port only)
Operational Temperature -40 °C to +85 °C
MKR Headers Use any of the existing industrial MKR shields on it
High-density Connectors Two 80 pin connectors will expose all of the board's peripherals to other devices
Camera Interface 8-bit, up to 80 MHz
ADC 3× ADCs with 16-bit max. resolution (up to 36 channels, up to 3.6 MSPS)
DAC 2× 12-bit DAC (1 MHz)
USB-C Host / Device, DisplayPort out, High / Full Speed, Power delivery



CPUは"STM32H747XI"で Cortex M7 と Cortex M4 のデュアルコアです。STマイクロのホームページで仕様を確認してみると、両方あわせて1,327 DMIPS なのでSPRESENSEにはやや及ばないレベル。(SPRESENSEはM4@300DMIPS x 6 = 1,800DMIS)

RAMとROMの情報がないので、こちらのサイトで確認しました。





SDRAM:64MB / Flash128M-bit で、かなりリッチです。ただSDRAMを使っているので低消費電力はあまり期待できないですね。残念ながら。

おさらいはここまでで、そのシールドが初登場しました。その名も "ARDUINO PORTENTA VISION SHIELD"。名前がかっこいいですね。カメラとEther、ステレオマイクが搭載されています。


pritenta-vision-shield.jpg

CameraHimax HM-01B0 camera module (manufacturer site)
Resolution324 x 324 active pixel resolution with support for QVGA
Image sensorHigh sensitivity 3.6μ BrightSense[トレードマーク] pixel technology
Microphone2 x MP34DT05 (datasheet)
Length66 mm
Width25 mm
Weight11 gr



イメージセンサーの解像度が想像以上に小さい。データシートを確認すると、QVGA 30FPS で 2mW 以下なのでかなり省電力ですが、本体の消費電力がイマイチなのでここで頑張ってもなぁという感じはします。





ひょっとして Global Shutter? とも思ったのですが、残念ながら Rolling Shutter。HDRというわけでもないし。うーん、なんのために使うのかよくわからない。


マイクのほうも確認をしてみました。データシートを確認すると、これはデジタルMEMSマイクですね。となると他のアナログセンサーや指向性マイクの接続は無理そうです。


mems.png


マイクは設置する場所が大事なので嵌め殺しにせずに、コネクタで接続できるようにしてほしかったなぁ。かなり残念。


価格は"Portenta H7"と"Vision Shield"両方あわせて、約 $150 (15,750円:105円/ドル)、SPRESENSEのメインボード、カメラ、LTEボードをあわせると 16,980 円。これぐらいのクラスになるとこれくらいの値段が妥当なのかな。

Arduino Portenta H7は Ether, WiFi/BLE を使えるけど消費電力がもう致命的。一方、SPRESENSEは5百万画素カメラ、アナログマイク4チャンネル、GPS付きで超低消費電力だけど、LTEボードにSIMがいるってことを考えると、どっこいどっこいかな。

プロ向けにとっては、最後は消費電力が決め手になりそう。SPRESENSEのWiFI/BLEやEtherは必要に応じて追加できるし。

ということで和洋のソックリさん対決は、ホビー向けにはArduino Portenta に軍配。低消費電力を追い求めるIoT向けにはSPRESENSEに軍配。という痛み分けの結果となりました。
(○ ̄ ー  ̄○ )





Arduinoをはじめよう 第3版 (Make:PROJECTS)

Arduinoをはじめよう 第3版 (Make:PROJECTS)

  • 出版社/メーカー: オライリージャパン
  • 発売日: 2015/11/28
  • メディア: 単行本(ソフトカバー)



Arduinoをはじめようキット

Arduinoをはじめようキット

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

  • 出版社/メーカー: スプレッセンス(Spresense)
  • メディア: Tools & Hardware




nice!(25)  コメント(0) 
共通テーマ:趣味・カルチャー

【悲報】SPR倒立振子の反省点途中まとめ(+モータードライバ死亡(´;ω;`)) [ロボット]

SPRESENSEで倒立振子にチャレンジしようと思い作っていたモータードライバを焦がしてしまいました。うっかり電源を逆に差した瞬間にプシュー…(・・)


sDSC_1320.jpg


今、手持ちの予備のモータードライバはなく、相変わらず私はテレワークなので、買い出しに行くきっかけもなく、しばらくお預けになりそうです。惜しいところまで来たのですが残念。


sDSC_1321.jpg


ただ、ここで倒立振り子を作る上での注意点ならびに設計の肝が分かってきたので、少しまとめたいと思います。


■ 倒立振子の重心は下のほうに!
メカを検討する際は倒立振り子の重心は下の方にいくようにしましょう。私の機体は上に回路やバッテリなどを当初置くようにしていたのですが、トップヘビーになりすぎてバランスがとれませんでした。

冷静に考えればすぐに分かるのですが、重心に上にあると倒れるときの速度が早く、反応の鈍いDCモーターでは追従できません。センサーの反応が早くてもモーターが反応できないので制御はできません。


■ センサーの位置は重心近くに!
センサーの位置が上にあると、倒れるときに加速度センサーの値が大きくなり、制御が過剰に反応します。倒立振子の場合、細かい角度に反応するようにするためPIDの係数は大きくなりがちです。そのため、ちょっとの変化に大きく反応してしまいます。

重心に近ければ加速度センサーではなくジャイロの変化が支配的になるので、制御しやすくなります。


■ DCモーターには電圧可変出力のモータードライバで
今回、DCモーターの制御はFETを経由してPWMで出力するドライバを使いました。PWMだと微小な値の場合、DCモーターを起動する電力が不足するため、最初に十分な電圧を加え、その後、パルスを送るようにする必要がありました。

このような制御だと連続して前後に変化する値の場合は、DCモーターの反応が悪いので一瞬ブレーキがかかるようになってしまい、スムーズな動きを実現するのは極めて難しかったです。

またモータードライバとPWMの関係もリニアではないため、非線形にモーターを制御する必要があります。倒立振子のように微妙なバランスを取るために微小パワーで正負制御する用途には向かないようです。

DCモーターを制御する場合は、DRV8830のようにインターフェースから数値を指定して出力電圧を変化させるモータードライバを選択したほうが無難かも知れません。


■ 制御間隔を短くすればいいもんでもない
DCモーターは反応が悪いです。ですのでセンサー処理がいくら早くフィードバックしてもモーターがついてこれません。今回使ったモータードライバはモーターの駆動に癖があるのでそれで時間がかかってしまいます。

また、逆方向に動かそうとしても、PWMでは慣性力をはるかに上回るパワー(パワー0になる期間があるため)が伝えられないため、俊敏に動かせません。モータードライバの癖にあわせて制御間隔を調整する必要がありそうです。


■ PID制御式の選択
今回は、PID制御の式は次の2種類について試してみました。

一般的なPID制御式

\[
Power(t) = KP (\theta (t) - offset) + KD \int_0^t (\theta(t) - offset)\,dt + KI \frac{d (\theta(t) - offset)}{dt}\
\]

センサー値を使ったPID制御式

\[
Power(t) = K1 (\theta(t) - offset) + K2 \omega(t) + K3 \nu(t) + K4 \int_0^t \nu(t)\,dt
\]

ここでθは車体の傾き。オフセットは傾きの初期値、ω は角速度、v は並進速度になります。

両方ためしてみたのですが、どうもセンサー値を使ったPIDの制御式のほうが安定するようです。ただ、センサー値に敏感に反応するのでセンサーは重心に近いところに置いたほうが無難です。


■ フィルターはMadgwickのほうが良い?
SPRESENSEはサブコアがあるのでフィルタ処理は計算し放題だったので、Kalman、Madgwick、Complemntary の三種類を同時に処理して値を返すようにしました。

Kalmanフィルターは遅延があるので、トップヘビー+FET型モータードライバというハンディキャップがある中ではかなり苦しみました。

Madgwickフィルターは微小な変化には反応が早いので倒立振子には向いてそうです。ただ、大きな変化では収束に時間がかかるので、扱いにはやや注意が必要です。


以上の反省点をふまえ、SPR倒立振子2を検討したいと思います!
( ・ิω・ิ)





Arduino電子工作: 2輪自立ロボットカーの製作

Arduino電子工作: 2輪自立ロボットカーの製作

  • 作者: 大西 和則
  • 出版社/メーカー:
  • 発売日: 2019/08/21
  • メディア: Kindle版






倒立振子制御学習用ロボット ビュートバランサー2 [学習教材] [vstone]

倒立振子制御学習用ロボット ビュートバランサー2 [学習教材] [vstone]

  • 出版社/メーカー: Vstone(ヴイストン株式会社)
  • メディア:




nice!(24)  コメント(0) 
共通テーマ:趣味・カルチャー

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。