SSブログ

二月の電子工作「SPRESENSEで音響通信」ー送受信の検討ー [SPRESENSE]

SPRESENSEの弱点である通信を音で実現しようと始めたこのプロジェクト。今回は、その基本方針をたててみました。


sDSC01982.jpg



■ 通信方法はFSKを採用
通信方法は周波数変調方式のFSKを採用します。FSKは次のように二つの周波数をそれぞれ"0"と"1"に割り当てて、データを送信する方法です。"1"に相当する周波数を"mark"、"0"に相当する周波数を"space"と呼ばれています。


FSK.png


データを送信するには、SPRESENSEからある特定の二つの周波数を生成し出力しなければなりません。BeatBoxのように、正弦波のMP3データを読み込んで再生という方法もありますが、スピードが出そうにないし、そもそも技術的にダサい。。



■ 送信は "AudioOscillator" ライブラリを活用
世の中にはいろいろと達人がいるもので、TomonobuHayakawa氏がSPRESENSEで特定の周波数音を発生させる、"AudioOscillator"というArduinoライブラリをGitHubで提供してくれています。


s_github.png TomonobuHayakawa/Spresense-Playground

Contribute to TomonobuHayakawa/Spresense-Playground development by creating an account on GitHub.
github.com



実はこのライブラリは大分前から目をつけていて、そのうちシンセサイザーを作りたいなと思ったりしています。(ラズパイシンセサイザーかっこいいので、SPRESENSEでも実現したい~)

送信側はこのライブラリを使うことで特定の2つの周波数を生成できそうです。ライブラリの使い方などはまだ良くわかっていませんが、サンプルは簡単に動いたので期待が持てそうです。



■ 受信間隔は周波数変調間隔の1/2以下
データの受信は、マイク入力で音を拾ってFFT解析でピーク周波数を検出することで "0:space" もしくは "1:mark" を検出するようにしたいと思います。でも、送信側の周波数の変調間隔と受信側の音のキャプチャ間隔はよく考える必要がありそう…。

簡単に考察すると、変調間隔と同じ "n msec" で受信間隔をとると、”0” と "1" の周波数が最悪50%ずつ混在してしまい ”mark/space" を判別できなくなりそうです。なので、受信間隔は最低でも "n/2 msec" である必要がありそうです。


2021-02-08 (2).png


これをもとに、送信側の変調間隔と受信側のサンプリング周波数とバッファ数など決めていきたいと思います。”AudioOscillator” と ”FFT” がうまく共存できれば、ループバックで検証ができるところまで行けるかも?

とりあえず、今日はここまで~。
(^^)/~



関連リンク
二月の電子工作「SPRESENSEで音響通信」
https://makers-with-myson.blog.ss-blog.jp/2021-02-02
二月の電子工作「SPRESENSEで音響通信」ー音響ループバックを試すー
https://makers-with-myson.blog.ss-blog.jp/2021-02-14
二月の電子工作「SPRESENSEで音響通信」ーFSKによるループバック・データ通信の実現ー
https://makers-with-myson.blog.ss-blog.jp/2021-02-22
二月の電子工作「SPRESENSEで音響通信」ーPCとSPRESENSE間の音響無線通信の実現!ー
https://makers-with-myson.blog.ss-blog.jp/2021-02-28

SPRESENSE でピンマイクを使えるようにしてみた 
https://makers-with-myson.blog.ss-blog.jp/2019-10-05
SPRESENSE にピンマイクをつけて録音してみた!
https://makers-with-myson.blog.ss-blog.jp/2019-10-12





C言語によるディジタル無線通信技術

C言語によるディジタル無線通信技術

  • 作者: 幸宏, 神谷
  • 出版社/メーカー: コロナ社
  • 発売日: 2010/11/22
  • メディア: 単行本



SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

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




タグ:Spresense Modem
nice!(23)  コメント(0) 

二月の電子工作「SPRESENSEで音響通信」 [SPRESENSE]

SPRESENSEの最大の弱点は何かというと通信機能!BLEやWiFiのアドオンボードはたくさんあるけど、できれば追加投資なしで通信ができたら…。


sDSC01982.jpg


ということで、今月はできるかどうか分かりませんが、SPRESENSEのメインボードと拡張ボードだけで通信を実現したいと思います。せっかく音が得意なSPRESENSEなので、FSK通信にチャレンジ!(まぢ出来るの??)

通信方式は、非同期シリアル通信で一般的に使われている、”パリティなし”、”データ8ビット”、”ストップビット1ビット”の一択限定です。


UART_N8S1.jpg


ボーレート(通信速度)はどれくらい出るか想像もできないので、出来高払いということで。通信相手はどうしようかなぁ。宣言したはいいものの、今月中にできるかどうか無茶苦茶不安。。。
(。-`ω´-)



関連リンク
二月の電子工作「SPRESENSEで音響通信」ー送受信の検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-02-07
二月の電子工作「SPRESENSEで音響通信」ー音響ループバックを試すー
https://makers-with-myson.blog.ss-blog.jp/2021-02-14
二月の電子工作「SPRESENSEで音響通信」ーFSKによるループバック・データ通信の実現ー
https://makers-with-myson.blog.ss-blog.jp/2021-02-22
二月の電子工作「SPRESENSEで音響通信」ーPCとSPRESENSE間の音響無線通信の実現!ー
https://makers-with-myson.blog.ss-blog.jp/2021-02-28

SPRESENSE でピンマイクを使えるようにしてみた 
https://makers-with-myson.blog.ss-blog.jp/2019-10-05
SPRESENSE にピンマイクをつけて録音してみた!
https://makers-with-myson.blog.ss-blog.jp/2019-10-12





SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

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




タグ:Spresense Modem
nice!(26)  コメント(0) 

一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーついに完成!ー [SPRESENSE]

SPRESENSEのタイムラプスカメラ完成しました!当初のスケッチとは大きく変わってしまいましたが、構想よりも可愛らしくできあがりました。

大きさは本体が58mm x 29mm x 26mm、カメラ部が31mm x 31mm 。重量は驚異の37g!300mAhのバッテリー内蔵で、タイムラプス画像はAVIで記録できます!


sADSC02027.jpg


横置きももちろん可能です。


sDSC02029.jpg


ソーラーパネルで充電しながらを想定し、お尻もあげられるようにしました。ペンギンみたいでかわいい。


sEDSC02032.jpg


さっそく、今宵の月を1分単位で1時間半ほど撮影してみました!





では制作過程のご紹介です。


■ 筐体デザインの構想
当初、スティック型を目指しましたが、プログラム用USBの位置がカメラの方向とかぶるので背後に置くのは無理。バッテリはソーラー充電も考えると尻尾につけたい。


original_idea.png


いろいろ悩んだ末に、カメラと本体を分離することにしました。録画開始ボタンも電源ON/OFFで代用し、無くすことにしました。

そのときのスケッチの様子です。(本体だけですが、、、カメラ部は次ページにスケッチしていますが、見せるほどのものではないので省略しました)


sDSC02013.jpg




■ Fusion360 で筐体デザイン
Fusion360を使って筐体デザインをしていきます。電源スイッチはボンドで固めてしまったので、位置の調整に苦労しました。


SprTimelapseBodyDesign.jpg


カメラ部は基板むき出しも考えたのですが、ちょうど黒いM2ネジがあったので、レンズの黒とネジの黒でデザイン的に映えるかなと思い、表面を覆うことにしました。


SprTimelapseCamDesign.jpg


余談ですが、Fusion360のマウス操作とCuraのマウス操作を一緒にできないものですかね。とっても煩わしい。


■ 我が家のオンボロ3Dプリンタで出力
我が家のオンボロ3Dプリンタをフル稼働。このプリンタ、筐体本体がABS樹脂で3Dプリンタで出力したもの。もう5年目で筐体が経年劣化で伸縮し、ホットベッドの支柱が微妙にねじれてしまいノズルとホットベッドの間隔が均一でなくなっています。なのでプリント品質がイマイチ…


sDSC02011.jpg


出力したパーツをニッパで切り出しました。遠目で見るときれいに見えても近くでみると”あ~”って感じ。3Dプリンタも激安になってきたから、そろそろ買い直そうかな。


sDSC02016.jpg



■ 本体の組み上げ
結構、攻めた設計をしたので、本体はギチギチ。プリンタの出力誤差もあって、SPRESENSEメインボードの場所は微妙に狭く、無理やりねじ込みました。これで壊れないのはさすがソニー製。(ラズパイカメラは無理やりねじ込んで、あっけなく壊れた悲しい思い出が…)

一方、バッテリは膨れるので部屋の大きさは余裕目に用意しました。


sDSC02017.jpg


カメラはカバーと腕を接着剤で固めて、ネジ止めするだけ。楽ちん。


sDSC02025.jpg


最後に足をつけて完成です!充電用のUSBコネクタが曲がっているのはご愛嬌。


sDSC02026.jpg



■ タイムラプスのソフトウェアを作る
JPG画像をSDカードに記録していくだけにしようかなとも思ったのですが、せっかくなら映像で記録し、すぐにPCで見れるようにしたい。ということでAVIで記録するようにしました。昔作ったコードを流用してます。



#include <SPI.h>
#include <SPISD.h>
#include <Camera.h>
#include <LowPower.h>
#include <RTC.h>


SpiSDClass SD(SPI5);
SpiFile infFile;
SpiFile aviFile;

static const String aviFilename = "movie.avi";
static const String infFilename = "info.txt";
static const int img_width = 1280;
static const int img_height = 960;

static const uint8_t WIDTH_1 = (img_width & 0x00ff);
static const uint8_t WIDTH_2 = (img_width & 0xff00) >> 8;
static const uint8_t HEIGHT_1 = (img_height & 0x00ff);
static const uint8_t HEIGHT_2 = (img_height & 0xff00) >> 8;

static uint16_t rec_frame_addr = 0x00;
static uint16_t movi_size_addr = 0x08;
static uint16_t total_size_addr = 0x10;
static uint32_t rec_frame = 0;
static uint32_t movi_size = 0;
static uint16_t exposure_time = 1000; // 100 msec
static uint16_t interval_time = 60; // 60 sec

#define TOTAL_FRAMES 300
#define AVIOFFSET 240

const char avi_header[AVIOFFSET+1] = {
  0x52, 0x49, 0x46, 0x46, 0xD8, 0x01, 0x0E, 0x00, 0x41, 0x56, 0x49, 0x20, 0x4C, 0x49, 0x53, 0x54,
  0xD0, 0x00, 0x00, 0x00, 0x68, 0x64, 0x72, 0x6C, 0x61, 0x76, 0x69, 0x68, 0x38, 0x00, 0x00, 0x00,
  0xA0, 0x86, 0x01, 0x00, 0x80, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
  0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  WIDTH_1, WIDTH_2, 0x00, 0x00, HEIGHT_1, HEIGHT_2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54, 0x84, 0x00, 0x00, 0x00,
  0x73, 0x74, 0x72, 0x6C, 0x73, 0x74, 0x72, 0x68, 0x30, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x73,
  0x4D, 0x4A, 0x50, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x66,
  0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, WIDTH_1, WIDTH_2, 0x00, 0x00, HEIGHT_1, HEIGHT_2, 0x00, 0x00,
  0x01, 0x00, 0x18, 0x00, 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x84, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54,
  0x10, 0x00, 0x00, 0x00, 0x6F, 0x64, 0x6D, 0x6C, 0x64, 0x6D, 0x6C, 0x68, 0x04, 0x00, 0x00, 0x00,
  0x64, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54, 0x00, 0x01, 0x0E, 0x00, 0x6D, 0x6F, 0x76, 0x69,
  0x00
};


static void inline uint32_write_to_aviFile(uint32_t v) { 
  char value = v % 0x100;
  aviFile.write(value);  
  v = v >> 8; 
  value = v % 0x100;
  aviFile.write(value);  
  v = v >> 8;
  value = v % 0x100;
  aviFile.write(value);  
  v = v >> 8; 
  value = v;
  aviFile.write(value);
}


void setup() {
  Serial.begin(115200);
  while (!SD.begin()) {
    Serial.println("Insert SD Card");
  }

  LowPower.begin();
  RTC.begin();
  bootcause_e bc = LowPower.bootCause();

  digitalWrite(LED3, HIGH);
  digitalWrite(LED2, HIGH);
  
  if (SD.exists(infFilename)) {
    infFile = SD.open(infFilename, FILE_READ);
    if (!infFile) {
      Serial.println("Information File Open Error for reading");
      while(1);
    }
    rec_frame = infFile.readStringUntil('\n').toInt();
    movi_size = infFile.readStringUntil('\n').toInt();
    exposure_time = infFile.readStringUntil('\n').toFloat();
    interval_time = infFile.readStringUntil('\n').toInt();
    Serial.println("Read Rec Frame: " + String(rec_frame));
    Serial.println("Read Movie Size: " + String(movi_size));
    Serial.println("Read Exposure Time: " + String(exposure_time));
    Serial.println("Read Interval Time: " + String(interval_time));
    infFile.close();
  } else {
    // default
    // rec_frame = 0;
    // movi_size = 0;
    // exposure_time = 1000; /* 100msec */ 
    // interval_time = 60;  /* sec */
  }

   // check for recording for the first time.
  if (bc != DEEP_RTC) {
    Serial.println("Power on reset");
    if (SD.exists(aviFilename)) {
      SD.remove(aviFilename);
      rec_frame = 0;
      movi_size = 0;
      Serial.println("removed " + aviFilename);
    }
  } 
  aviFile = SD.open(aviFilename ,FILE_WRITE);
  if (!aviFile) {
    Serial.println("Movie File Open Error!");
    while(1);
  }
  
  if (rec_frame == 0) {
    Serial.println("First time: write header");
    aviFile.write(avi_header, AVIOFFSET);
    sleep(3); // wait for 3sec
  }

  Serial.println("Recording...");
  theCamera.begin();
  theCamera.setAbsoluteExposure(exposure_time); // 0.1 sec
  theCamera.setStillPictureImageFormat(
      img_width
     ,img_height
     ,CAM_IMAGE_PIX_FMT_JPG);
}

void loop() {
  
  CamImage img = theCamera.takePicture();
  if (!img.isAvailable()) {
    Serial.println("faile to take a picture");
    return;
  }

  if (rec_frame != 0) {
    aviFile.seek(aviFile.size());
  }
  aviFile.write("00dc", 4);

  uint32_t jpeg_size = img.getImgSize();
  uint32_write_to_aviFile(jpeg_size);
  
  aviFile.write(img.getImgBuff() ,jpeg_size);
  movi_size += jpeg_size;
  ++rec_frame;
  theCamera.end(); // to save power consumption

  /* Spresense's jpg file is assumed to be 16bits aligned 
   * So, there's no padding operation */

  float duration_sec = 0.1; // fix 10fps for Timelapse
  float fps_in_float = 10.0f; // fix 10fps for Timelapse
  float us_per_frame_in_float = 1000000.0f / fps_in_float;
  uint32_t fps = round(fps_in_float);
  uint32_t us_per_frame = round(us_per_frame_in_float);

  /* overwrite riff file size */
  aviFile.seek(0x04);
  uint32_t total_size = movi_size + 12*rec_frame + 4;
  uint32_write_to_aviFile(total_size);

  /* overwrite hdrl */
  /* hdrl.avih.us_per_frame */
  aviFile.seek(0x20);
  uint32_write_to_aviFile(us_per_frame);
  uint32_t max_bytes_per_sec = movi_size * fps / rec_frame;
  aviFile.seek(0x24);
  uint32_write_to_aviFile(max_bytes_per_sec);

  /* hdrl.avih.tot_frames */
  aviFile.seek(0x30);
  uint32_write_to_aviFile(rec_frame);
  aviFile.seek(0x84);
  uint32_write_to_aviFile(fps);   

  /* hdrl.strl.list_odml.frames */
  aviFile.seek(0xe0);
  uint32_write_to_aviFile(rec_frame);
  aviFile.seek(0xe8);
  uint32_write_to_aviFile(movi_size);

  aviFile.close();

  if (SD.exists(infFilename)) SD.remove(infFilename);
  infFile = SD.open(infFilename, FILE_WRITE);
  if (!infFile) {
    Serial.println("Information File Open Error for writing");
    while(1);
  }
  infFile.println(String(rec_frame));
  infFile.println(String(movi_size));
  infFile.println(String(exposure_time));
  infFile.println(String(interval_time));
  infFile.close();
  Serial.println("Information File Update: ");
  Serial.println("Write Rec Frame: " + String(rec_frame));
  Serial.println("Write Movie Size: " + String(movi_size));

  Serial.println("Movie saved");
  Serial.println(" File size (kB): " + String(total_size));
  Serial.println(" Captured Frame: " + String(rec_frame)); 
  Serial.println(" Duration (sec): " + String(duration_sec));
  Serial.println(" Frame per sec : " + String(fps));
  Serial.println(" Max data rate : " + String(max_bytes_per_sec));

  digitalWrite(LED2, LOW);
  digitalWrite(LED3, LOW);
  LowPower.deepSleep(interval_time); // Go to deep sleep for 60 seconds
} 



最初はパラメータの保存にSpresense用のEEPROMを使おうと思ったのですが、なぜだかEEPROMを使うと自作のSPISDライブラリのファイルオープンでエラーが返って来てしまいます。何かが競合しているみたいです。もし、SPISDライブラリを使おうと考えている人がいたら注意してください。


■ 作り終えた感想
電源オンで勝手にタイプラプス画像を作ってくれる小さくて軽量なカメラは、想像以上に楽しいものに仕上がりました。何より電池の持ちがいいのが良い!これからどれくらい持つかは試してみたいと思いますが、ひょっとしたら一日くらい余裕かも。ソーラーパネルによって充電フリーにできないか試したいと思います。

露光なども自分で設定できるので、シチュエーションにあわせて調整できるのもいいですね。(露光時間などパラメータをSDカードの情報ファイルで設定できるようにしよう、そうしよう)

高感度カメラがつながると暗い場所の撮影にも使えるのでさらに用途が広がりそうです。どこか出してくれないかな。
( ̄ー ̄).。oO


関連リンク
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」
https://makers-with-myson.blog.ss-blog.jp/2021-01-04
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ー充電回路の検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-10
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーSDカードの検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-17
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーSDカードドライバの検討ー





SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

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



KKHMF SDカードスロットソケットリーダーモジュールArduino用 [並行輸入品]

KKHMF SDカードスロットソケットリーダーモジュールArduino用 [並行輸入品]

  • 出版社/メーカー: Apple Trees E-commerce co., LT
  • メディア:



nice!(21)  コメント(1) 

一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーSDカードドライバの検討ー [SPRESENSE]

前回、SPRESENSEのメインボードのSPIに接続するSDカード・アドオンボードを作りました。今回は、Arduino IDEでカメラ画像をSDカードに保存するためのSDカードドライバを検討しました。


DSC02005.JPG


■ Arduino のSDカードライブラリが使えない!?
Arduino の標準のSDカードライブラリがそのまま使えればラッキーだなと思ったのですが、SPIのインスタンスを渡すAPIがない。CSピンを指定するようになっています。

  class SDClass {
    ... snip ...
    public:
      boolean begin(uint8_t csPin = SD_CHIP_SELECT_PIN);
      boolean begin(uint32_t clock, uint8_t csPin);
  };
    ... snip ...


実装を見ると、ハードウェアはArduino UNO前提で、その他はソフトウェアSPIを使う様になっています。SPRESENSEのハードウェアSPIましてはSPI5はまず使えそうにありません。

で、オリジナルのSDライブラリを作り変えようと思ったらリードオンリ。それではとコピーして作り直そうとしたら名前がバッティングして使えない。

う~ん、困りました。


■ SPRESENSE用のSDカードライブラリを自作
いろいろ試行錯誤をしましたが、無理して使うより既存のArduinoの実装を参考にして作り直したほうが早いんじゃね?と思い、作り直すことにしました。Arduino の Library にしましたので、もしよかったら使ってやってください。恐らく拡張ボードのSPIも使えると思います。
(実は試してない( ^ω^)・・・)


s_github.png YoshinoTaro/SPISD

Contribute to YoshinoTaro/SPISD development by creating an account on GitHub.
github.com



■ 画像をSDカードに記録してみた!
カメラで撮影した画像をSDカードに記録するためのスケッチを作ってみました。このスケッチはサンプルにすでに入れているのですが、こちらでも紹介します。

このSDカードライブラリはクラス名が異なるのと、初期化時にSPIのインスンタンスを与えられるところ以外は従来のSDカードライブラリとまったく同じです。

#include <SPI.h>
#include <SPISD.h>
#include <Camera.h>

SpiSDClass SD(SPI5);

const int intPin = 0;
static bool bButtonPressed = false;
static int gCounter = 0;

void changeState () {
  bButtonPressed = true;
}

void setup() {
  Serial.begin(115200);
  if (!SD.begin(SPI_FULL_SPEED)) {
    Serial.println("SD.begin() failed");
    return;
  }

  theCamera.begin();
  theCamera.setStillPictureImageFormat(
    CAM_IMGSIZE_QUADVGA_H ,CAM_IMGSIZE_QUADVGA_V
   ,CAM_IMAGE_PIX_FMT_JPG);

  attachInterrupt(digitalPinToInterrupt(intPin) ,changeState ,FALLING);
}

void loop() {
  if (!bButtonPressed) return;  
  Serial.println("button pressed");
  digitalWrite(LED0, HIGH);
  CamImage img = theCamera.takePicture();
  if (img.isAvailable()) {
      char filename[16] = {0};
      sprintf(filename, "PICT%03d.JPG", gCounter);
      SpiFile myFile = SD.open(filename,FILE_WRITE);
      myFile.write(img.getImgBuff(), img.getImgSize());
      myFile.close();
      ++gCounter;
  }
  bButtonPressed = false;
  digitalWrite(LED0, LOW);
}


このスケッチは、ボタンを押すことで画像を記録するようになっています。かなりシンプルに使えるものになりました。実際に撮影してみた画像がこちら。


PICT001.JPG


なかなかいい感じで取れています。最終的にはタイムラプスにするので、ボタンではなく一定間隔でスリープから起動して記録していくようにしていきます。ここまで出来たので、残りの仕事は大したものではないでしょう。

しかし、記録にかなり時間がかかっています。何か実装がまずいのかも知れません…。とりあえず記録できているので、今の所はヨシとしましょう。

最後の難関は筐体の設計。いろいろと想定と変わってしまったので、最終的な形はスティックにはならなさそうです。簡単にはいかないですねぇ。Fusion360は久しぶりすぎて使えるか不安。
(´・ω・`)



関連リンク
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」
https://makers-with-myson.blog.ss-blog.jp/2021-01-04
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ー充電回路の検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-10
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーSDカードの検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-17
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーついに完成!ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-31





SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

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







nice!(25)  コメント(0) 

一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーSDカードの検討ー [SPRESENSE]

前回は、充電回路を検討しましたので、今回は映像を記録するためのSPRESENSE用のSDカードモジュールを作りました。小さくするためにメインのピンソケットに接続できるアドオン形式で作りました。


DSC01995.JPG


検討にあたり、@hikoalphaさんのArduinoでmicroSDカードを使うを参考にさせていただきました。


■ 使用した部材
(1)マイクロSDカードスロットDIP化キット
秋月電子で購入

https://akizukidenshi.com/catalog/g/gK-05488/

(2)8ビット双方向ロジックレベル変換モジュール
秋月電子で購入

https://akizukidenshi.com/catalog/g/gM-04522/

(3)0.3mm厚 極薄両面ガラス・ユニバーサル基板
秋月電子で購入

https://akizukidenshi.com/catalog/g/gP-09608/


(4)SPRESENSE用ピンヘッダ 13P
スイッチサイエンスで購入

https://www.switch-science.com/catalog/3940/


■ SPRESENSEメイン基板用の基板の準備
SDカード基板を乗せるためのベースとなる基板をつくります。SPRESENSEメイン基板に載せるための基板です。メイン基板に出ているSPIとSDカードを接続します。


SPISD.png


メイン基板の幅にあわせて、極薄ユニバーサル基板を切り取ります。この基板はハサミで切れるのでとっても便利。


DSC01994.JPG


ピンをつけて完成です。


DSC01986.JPG



■ SDカードとレベルシフタとベース基板をハンダで接続
SPRESENSEメインボードのIOは1.8V。SDカードのIOは3.3Vなのでレベルシフトしなければなりません。SDカードとレベルシフタとベース基板は次のように接続します。


SPISD2.png


念の為、ピンの対応を示します。

SPRESENSESDカード
SPI_CSCD/DAT3
SPI_MOSICMD
3.3VVDD
SPI_SCKCLK
GNDVSS
SPI_MISODAT0


小さく仕上げるためにSDカードとレベルシフタ、ベース基板は細いエナメル線(ポリウレタン銅線)で直接つなぎます。


DSC01993.JPG


エナメル線は表面のポリウレタンがきちんと溶けていないと接触不良を起こすので、ハンダで物理的につながったと思っても電気的にはつながっていないケースが多いです。テスターで接続を確認しましょう。

ハンダが下手くそなので、かなり苦労をしましたが、ようやく完成しました!


DSC01988.JPG



■ グルーガンでSDカードボードを固める
これら3つの部品をコンパクトにまとめるためにレベルシフタをSDカードモジュールの裏に折り返して貼り付けて、さらにベース基板とグルーガンで固めてしまいました。


DSC01989.JPG


ついでにバッテリーと充電器もグルーガンでまとめてしまいました。


DSC01990.JPG


SPRESENSEは低消費電力なので熱でグルーが溶けることはないでしょう。(きっと)


■ すべての電気パーツが完成!
すべての電気パーツがこれで完成しました。残るはメカとソフト。ソフトはなんとかなると思いますが、問題はメカだなぁ。思ったよりも厚みが出てしまった。縦3cmには収まらないかな。


DSC01992.JPG


今月中に終わるかなー???
(。-`ω´-)


関連リンク
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」
https://makers-with-myson.blog.ss-blog.jp/2021-01-04
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ー充電回路の検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-10
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーSDカードドライバの検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-24
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーついに完成!ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-31








TXS0108E レベルシフタ ピッチ変換済みモジュール

TXS0108E レベルシフタ ピッチ変換済みモジュール

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



SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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




nice!(26)  コメント(1) 

一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ー充電回路の検討ー [SPRESENSE]

「SPRESENSEでタイムラプスカメラ」の充電回路を作りました。300mAhのリチウムポリマー電池を充電しながら使うことができます。スイッチもつけているのでSPRESENSEをON/OFFすることもできます。


DSC01981.JPG


※リチウムポリマー電池は損傷すると激しく発火する非常に危険な電気部品です。工作する場合は自己責任で行ってください。


■ 使用した部材
(1) 300mAh 3.7V リチウムポリマーバッテリー(SX-3.7V-300MAH)
aitendoで購入

https://www.aitendo.com/product/9669

(2) LiPoバッテリー充電ボード(TP4056充電モジュール充電器)
Amazonで購入




(3) SPRESENSE用バッテリー用コネクタ(S2B-PH-K-S)
Amazonで購入

JST Sales America Inc. S2B-PH-K-S(LF)(SN)(5個セット)

JST Sales America Inc. S2B-PH-K-S(LF)(SN)(5個セット)

  • 出版社/メーカー: 日本圧着端子製造(JST)
  • メディア: Tools & Hardware



(4) バッテリコネクタ&コネクタ用ターミナル(PHR-2&BPH-002T-P0.5S)
Amazonで購入




(6) スイッチ(SS12D00)
aitendoで購入

https://www.aitendo.com/product/4200


■ コネクタの準備
コネクタはJSTのものを使います。押すのコネクタはSpresenseにつけて、メス側をバッテリにつけます。


DSC01964.JPG


最初にSpresenseにコネクタをつけます。SpresenseのGNDが強くてハンダのノリが悪いので根気強くハンダづけをしてください。


SpresenseWConnector.jpg


次にバッテリ用のコネクタを作ります。圧着ペンチを使ってケーブルを作ります。コネクタにケーブルをつけるときはプラスとマイナスに注意してください。


makeconnector.jpg


■ 充電回路とバッテリ、コネクタを接続する
充電回路とリチウムポリマー電池、コネクタ、(スイッチ)を準備します。(スイッチを撮影に入れるの忘れてました)


DSC01975.JPG


接続は次のように行います。バッテリのハンダづけはショートしないように注意深く行ってください。


TP4056.png


動作確認をしてみました。問題なく動作するようです。


ON_OFF.jpg


とりあえず、充電回路は解決しました。次はSDカードの検討ですね。


関連リンク
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」
https://makers-with-myson.blog.ss-blog.jp/2021-01-04
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーSDカードの検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-17
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーSDカードドライバの検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-24
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーついに完成!ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-31





SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



nice!(23)  コメント(0) 

一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」 [SPRESENSE]

これから毎月はじめに電子工作するものを宣言し、自分を奮い立たせていきたいと思います。

一月のお題は「SPRESENSEでタイムラプスカメラを作ろう」です。妄想しているのは次のようなカメラです。


StickCamera.png


バッテリーはLiPoバッテリを想定。充電回路もつけて充電可能なようにします。SDはSPI接続を想定。あまり速度ではでないので数秒間隔でのタイムラプスカメラとなると思います。

今の所見えている課題は、次の4点です。これから一つひとつクリアしていきたいと思います。

(1)充電回路の検討
(2)SDカード回路の検討
(3)SDカードアクセスライブラリの検討
(4)ケースの設計

(1)~(3)はなんとかなりそうですが、問題は(4)かなぁ。メカはド素人なので。
どんなものが出来るかはお楽しみに!
(^^)/~



関連リンク
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ー充電回路の検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-10
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーSDカードの検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-17
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーSDカードドライバの検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-24
一月の電子工作「SPRESENSEでタイムラプスカメラを作ろう」ーついに完成!ー
https://makers-with-myson.blog.ss-blog.jp/2021-01-31





SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

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




nice!(25)  コメント(0) 

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) 

SPRESENSEでフォトカプラを使ってみた! [SPRESENSE]

SPRESENSEでモーター等の大電流を必要とするデバイスを使用するのは躊躇しますよね。SPRESENSEは安くはないし、低消費電力に作られているので、大電流が流れる線がどんな形であれ物理的につながっているのは不安です。


心配.png


そんな不安を払拭するのが「フォトカプラ」です。フォトカプラは入力側にLEDが、出力側に受光素子があり、入力と出力を完全に絶縁した形で信号を伝えることができます。


PhotoCouplar.png
フォトカプラとは何ですか?


今回使ったのは東芝製の「TLP250」です。一つ百数十円なので、それで安全を買えるなら安いものです。


TLP250.png


次の回路を使って動作を確認しました。負荷として模型でよく使うDCブラシモーターを接続しました。12V を突っ込もうと思ったのですが、何かあってモーターを壊しても嫌なので、TLP250の定格外ですが5.0Vを突っ込んでみました。(動くかなー)


TLP250.png


SPRESENSEに書き込んだスケッチは極めて単純なものです。


void setup() {
  pinMode(1, OUTPUT);
  Serial.begin(115200);
  digitalWrite(1, LOW);
  sleep(3);
}

void loop() {
  digitalWrite(1, HIGH);
  Serial.println("HIGH");
  sleep(1);
  digitalWrite(1, LOW);
  Serial.println("LOW");
  sleep(3);
}



実際に動かしてみた様子がこちらです。





あっけなく動いてしまいました。TLP250 は入力側が 1.8V なのでメインから直接駆動できるのも魅力ですね。35V まで制御できるので、大型のモーターも動かせそうです。応用の幅がひろがりそうですね。
(^^)/~






SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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










nice!(24)  コメント(0) 

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