SSブログ
前の5件 | -

二月の電子工作「SPRESENSEで音響通信」ーFSKによるループバック・データ通信の実現ー [SPRESENSE]

前回は、SPRESENSEで音響ループバックを試してみました。ただ単に音を出してマイクで音を拾うというレベルです。今回はいよいよ、FSKによるループバック・データ通信に挑みます。


sDSC02037.jpg



■ FSKによるデータ送信部(モジュレータ)を実装
まずは送信の実装です。今回も前回と同じく”MARK”を 2kHz、”SPACE”を 1kHz に設定します。データの送信は、シリアルコンソールから入力された文字を送信するようにしました。



FSK.png



今回、受信側はサンプリングレート 48 kHz で 256 tap のFFTを使おうと考えているので、データ判定のために、1/48000 x 256 = 5.33msec 必要です。余裕をもって変調間隔は 40msec で試すことにしました。


FSK_modulation_rate.png


いろいろ試行錯誤したのですが、どうも Oscillator ライブラリは 10msecスリープ毎に active() 関数を呼ぶとFIFOのオーバーフローが起きなさそう。(※ハマりポイント その1)

処理時間を測ると、一つの active() とスリープで 20msec程度かかっている模様。で、普通ループで処理をまわそうと思いますよね、、、でも、なぜか処理時間がばらつきます。ここのコードです。


uint32_t last_time = 0;
void send_signal(uint16_t hz, int repeat = SIGNAL_DURATION) {
  theOscillator->set(0, hz);

#if 0
  for (int i = 0; i < repeat; ++i) {
    active();  
    usleep(ACTIVE_DURATION);
  }
#else  
  // interim measures
  active();  
  usleep(ACTIVE_DURATION);
  active();  
  usleep(ACTIVE_DURATION);
#endif

#if 0
  uint32_t cur_time = millis();
  Serial.println(String(cur_time - last_time));
  last_time = cur_time;
#endif
}



送信間隔を測ってみると、active関数とusleep関数を直接二度呼んだ場合は問題ありませんが、for ループでは処理時間がばらばら。なんで???(※ハマりポイント その2)


ActiveFuntion_Problem.png


実際に、受信側にどれだけ影響があるか確認するために、"0" と "1" が綺麗にならぶ大文字 'U' を送信し、FFTの結果を表示しててみました。


※acitve関数を2回コール(正常)
FSK_Send_U_OK.png


ご覧のようにループをすると結果がおかしくなり、使い物になりません。うーん、なんでだろう?ちょっと奥が深そうなので、ここは深追いせずに実装を優先します。


※for loop で active関数を2回コール(異常)
FSK_Send_U_NG.png


少し格好悪いですがactive関数を二重に呼ぶようにようにしてとりあえず実装完了としました。


#include <MediaRecorder.h>
#include <MemoryUtil.h>
#include <AudioOscillator.h>
#include <OutputMixer.h>
#include <arch/board/board.h>
#include <FFT.h>

#define ANALOG_MIC_GAIN  0 /* +0dB */

#define FFT_LEN 256
#define CHANNEL_NUM 1

#define SPACE (1000)
#define FSK_CENTER (1500)
#define MARK  (2000)
#define ACTIVE_DURATION (10000) /* usec  */
#define SIGNAL_DURATION (2) /* x ACTIVE_DURATION  */


Oscillator  *theOscillator;
OutputMixer *theMixer;
MediaRecorder *theRecorder;

FFTClass FFT;
static float pDst[FFT_LEN];

static const uint32_t rec_bitrate = AS_BITRATE_48000;
static const uint32_t rec_sampling_rate = AS_SAMPLINGRATE_48000;
static const uint8_t  rec_channel_num = CHANNEL_NUM;
static const uint8_t  rec_bit_length = AS_BITLENGTH_16;
static const int32_t  buffer_size = FFT_LEN * sizeof(int16_t);
static const uint32_t rec_wait_usec = buffer_size * (1000000 / rec_sampling_rate) / 2;
static uint8_t        s_buffer[buffer_size*2];

static bool rec_done_cb(AsRecorderEvent e, uint32_t r1, uint32_t r2) {
  // printf("mp cb %x %x %x\n", e, r1, r2);
  return true;
}

static void rec_attention_cb(const ErrorAttentionParam *p) {  
  if (p->error_code >= AS_ATTENTION_CODE_WARNING)   {
    Serial.println("Attention!");
  }
}

static void mixer_done_cb(MsgQueId id, MsgType type, AsOutputMixDoneParam *p) {
  // Serial.println("mixer done callback"); 
  return; 
}

static void mixer_send_cb(int32_t id, bool is_end) { 
  // Serial.println("mixer send callback"); 
  return; 
}

static void audioReadFrames() {
  uint32_t read_size;
  int er;
  while (true) {
    er = theRecorder->readFrames(s_buffer, buffer_size, &read_size); 
    if (er != MEDIARECORDER_ECODE_OK && er != MEDIARECORDER_ECODE_INSUFFICIENT_BUFFER_AREA) {
      Serial.println("Recording Error");
      theRecorder->stop();
      theRecorder->deactivate();
      theRecorder->end();
      break;
    } 
    
    // Serial.println("buffer_size:" + String(buffer_size) + " read_size:" + String(read_size));
    if (read_size < buffer_size){
      usleep(rec_wait_usec);
      continue;  
    }

    FFT.put((q15_t*)s_buffer, FFT_LEN);
    FFT.get(pDst, 0);

    uint32_t index;
    float maxValue;
    int max_line = FFT_LEN/2.56;
    arm_max_f32(pDst, max_line, &maxValue, &index);

    float peakFs = index  * (rec_sampling_rate / FFT_LEN);
    uint8_t sbit;
    if (peakFs > FSK_CENTER) {  /* MARK */
      sbit = 0x01;      
    } else if (peakFs <= FSK_CENTER) { /* SPACE */
      sbit = 0x00;
    }
    Serial.println(sbit);
  }
}

static bool active() {
  const uint32_t sample = 480;
  int er;

  for (int i = 0; i < 5; i++) {
    AsPcmDataParam pcm;  /* get PCM */
    
    er = pcm.mh.allocSeg(S0_REND_PCM_BUF_POOL, (sample*2*2));
    if (er != ERR_OK) break;
    theOscillator->exec((q15_t*)pcm.mh.getPa(), sample);
    pcm.identifier = 0;
    pcm.callback = 0;
    pcm.bit_length = 16;
    pcm.size = sample*2*2;
    pcm.sample = sample;
    pcm.is_end = false;
    pcm.is_valid = true; 
    er = theMixer->sendData(OutputMixer0, mixer_send_cb, pcm);
    if (er != OUTPUTMIXER_ECODE_OK) {
      Serial.println("OutputMixer send error: " + String(er));
      return false;
    }
  }
  return true;
}

void setup() {
  Serial.begin(115200);
    
  initMemoryPools();
  createStaticPools(MEM_LAYOUT_RECORDINGPLAYER);
  
  theOscillator = new Oscillator();
  theOscillator->begin(SinWave, 1);
  theOscillator->set(0, 0);
  theOscillator->set(0, 700, 200, 50, 400); // attack=0, decay=700, sustain=50, release=400
  theOscillator->lfo(0, 4, 2);
  
  theMixer = OutputMixer::getInstance();
  theMixer->activateBaseband(); 
  theMixer->create();
  theMixer->setRenderingClkMode(OUTPUTMIXER_RNDCLK_NORMAL);
  theMixer->activate(OutputMixer0, HPOutputDevice, mixer_done_cb);
  theMixer->setVolume(-160, 0, 0);
  board_external_amp_mute_control(false);    /* Unmute */

  FFT.begin(WindowRectangle, rec_channel_num, 0);

  theRecorder = MediaRecorder::getInstance();
  theRecorder->begin(rec_attention_cb);
  theRecorder->setCapturingClkMode(MEDIARECORDER_CAPCLK_NORMAL);
  theRecorder->activate(AS_SETRECDR_STS_INPUTDEVICE_MIC, rec_done_cb);
  theRecorder->init(AS_CODECTYPE_LPCM, rec_channel_num
    , rec_sampling_rate, rec_bit_length, rec_bitrate, "/mnt/sd0/BIN");
  theRecorder->setMicGain(ANALOG_MIC_GAIN);
  theRecorder->start();
  // Serial.println("rec wait usec: " + String(rec_wait_usec));
  task_create("audio recording", 120, 1024, audioReadFrames, NULL);
}


uint32_t last_time = 0;
void send_signal(uint16_t hz, int repeat = SIGNAL_DURATION) {
  theOscillator->set(0, hz);

#if 0
  for (int i = 0; i < repeat; ++i) {
    active();  
    usleep(ACTIVE_DURATION);
  }
#else  
  // interim measures
  active();  
  usleep(ACTIVE_DURATION);
  active();  
  usleep(ACTIVE_DURATION);
#endif

#if 0
  uint32_t cur_time = millis();
  Serial.println(String(cur_time - last_time));
  last_time = cur_time;
#endif
}

void send_char(uint8_t c, int n = SIGNAL_DURATION) {
  send_signal(SPACE, n);  // send start bit
  for (int n = 0; n < 8; ++n, c = c >> 1) { /* LSB fast */
    if (c & 0x01) { /* mark (1) */
      send_signal(MARK, n);
    } else { /* space (0) */
      send_signal(SPACE, n);
    }
  } 
  send_signal(MARK, n);  // send stop bit
}
  
void loop() {
  send_signal(MARK); // default level is mark
  if (Serial.available()) {
    char c = Serial.read();
    send_char(c);
  } 
}




せっかくなので、いろいろなデータを入力したときのFFTの出力波形をキャプチャしてみました。こういう絵面って、見てるだけでも飽きませんよね…。
(ところで、SSブログって動画アップロードできたんですね。、今更知った… ̄▽ ̄;)





余談ですが、この実験を通して大文字”U"が大好きになりました。データ処理システムを作る人にとって、はなくてはならない相棒ですね。^^



■ FSKのデータ受信部(デモジュレータ)を実装
データ受信部はサンプリング周波数 48 kHz で、FFTが 256 tap の 5.3msec で単位で処理を行います。どのようににしてビット情報をフェッチするか色々と悩んだのですが、極めてハードウェア的なアプローチで行うことにしました。

スタートビットからカウンターを回して、ある間隔ごとにデータをフェッチするという方法です。(この結論に辿り着くまでに半日かかった…シンプル・イズ・ザ・ベスト!)


FSK_FETCH_TIMING.png


それに加えて、データ処理にステートマシンがあったほうが設計が楽になるので、簡単なステートマシンも設計しました。


FSK_STATE_MACHINE.png


実装は送信側もあるのでかなり長くなってしまっていますが晒しておきます。ライブラリ化したらGitHub にでも置こうと思います。


#include <MediaRecorder.h>
#include <MemoryUtil.h>
#include <AudioOscillator.h>
#include <OutputMixer.h>
#include <arch/board/board.h>
#include <FFT.h>

#define ANALOG_MIC_GAIN  0 /* +0dB */

#define FFT_LEN 256
#define CHANNEL_NUM 1

#define SPACE (1000)
#define FSK_CENTER (1500)
#define MARK  (2000)
#define ACTIVE_DURATION (10000) /* usec  */
#define SIGNAL_DURATION (2) /* x ACTIVE_DURATION  */

#define IDLE_STATE     (0)
#define STARTBIT_STATE (1)
#define BITREC_STATE   (2)
#define STOPBIT_STATE  (3)
#define FETCH_INTERVAL (8)
#define MSBBIT_INDEX   (7)

// #define DEBUG_ENABLE

Oscillator  *theOscillator;
OutputMixer *theMixer;
MediaRecorder *theRecorder;

FFTClass FFT;
static float pDst[FFT_LEN];

static const uint32_t rec_bitrate = AS_BITRATE_48000;
static const uint32_t rec_sampling_rate = AS_SAMPLINGRATE_48000;
static const uint8_t  rec_channel_num = CHANNEL_NUM;
static const uint8_t  rec_bit_length = AS_BITLENGTH_16;
static const int32_t  buffer_size = FFT_LEN * sizeof(int16_t);
static const uint32_t rec_wait_usec = buffer_size * (1000000 / rec_sampling_rate) / 2;
static uint8_t        s_buffer[buffer_size*2];

static uint8_t frame_cnt = 0;
static uint8_t fetch_timing = 1; 
static uint8_t bpos = 0;
static uint8_t cur_state = IDLE_STATE;
static char    output = 0;

static bool rec_done_cb(AsRecorderEvent e, uint32_t r1, uint32_t r2) {
  // printf("mp cb %x %x %x\n", e, r1, r2);
  return true;
}

static void rec_attention_cb(const ErrorAttentionParam *p) {  
  if (p->error_code >= AS_ATTENTION_CODE_WARNING)   {
    Serial.println("Attention!");
  }
}

static void mixer_done_cb(MsgQueId id, MsgType type, AsOutputMixDoneParam *p) {
  // Serial.println("mixer done callback"); 
  return; 
}

static void mixer_send_cb(int32_t id, bool is_end) { 
  // Serial.println("mixer send callback"); 
  return; 
}

void debug_print(uint8_t sbit) {
#ifdef DEBUG_ENABLE
  static bool first_print = true;
  if (first_print) {
    Serial.println("state, sbit, bpos, fcnt");
    first_print = false;   
  }
  Serial.print(String(cur_state));
  Serial.print("," + String(sbit));
  Serial.print("," + String(bpos));
  Serial.print("," + String(frame_cnt));
  Serial.println();  
#endif
}

void idle_phase(uint8_t sbit) {
  if (sbit == 0) {
    cur_state = STARTBIT_STATE;
  }
  // debug_print(sbit);
  
  frame_cnt = 0;
  fetch_timing = 1;
  output = 0;
  return;
}

void startbit_phase(uint8_t sbit) {
  ++frame_cnt;
  if (frame_cnt != fetch_timing) return;
  debug_print(sbit);
  
  cur_state = BITREC_STATE;
  fetch_timing += FETCH_INTERVAL;
  return;
}

void bitrec_phase(uint8_t sbit) {
  ++frame_cnt;
  if (frame_cnt != fetch_timing) return;
  debug_print(sbit);  
  
  output = output | (sbit << bpos);
  fetch_timing += FETCH_INTERVAL;
  if (++bpos > MSBBIT_INDEX) {
    cur_state = STOPBIT_STATE;
  }
  return;
}


bool stopbit_phase(uint8_t sbit) {
  ++frame_cnt;
  if (frame_cnt != fetch_timing) return;
  debug_print(sbit);
  
  Serial.write(output);  // interim implementation
  frame_cnt = 0;
  bpos = 0;
  cur_state = IDLE_STATE;
  return;
}

static void audioReadFrames() {
  uint32_t read_size;
  int er;
  while (true) {
    er = theRecorder->readFrames(s_buffer, buffer_size, &read_size); 
    if (er != MEDIARECORDER_ECODE_OK && er != MEDIARECORDER_ECODE_INSUFFICIENT_BUFFER_AREA) {
      Serial.println("Recording Error");
      theRecorder->stop();
      theRecorder->deactivate();
      theRecorder->end();
      break;
    } 
    
    // Serial.println("buffer_size:" + String(buffer_size) + " read_size:" + String(read_size));
    if (read_size < buffer_size){
      usleep(rec_wait_usec);
      continue;  
    }

    FFT.put((q15_t*)s_buffer, FFT_LEN);
    FFT.get(pDst, 0);

    uint32_t index;
    float maxValue;
    int max_line = FFT_LEN/2.56;
    arm_max_f32(pDst, max_line, &maxValue, &index);

    float peakFs = index  * (rec_sampling_rate / FFT_LEN);
    uint8_t sbit;
    if (peakFs > FSK_CENTER) {  /* MARK */
      sbit = 0x01;      
    } else if (peakFs <= FSK_CENTER) { /* SPACE */
      sbit = 0x00;
    }
    
    switch(cur_state) {
     case IDLE_STATE:   idle_phase(sbit); break;
     case STARTBIT_STATE:  startbit_phase(sbit); break;
     case BITREC_STATE: bitrec_phase(sbit); break;
     case STOPBIT_STATE:   stopbit_phase(sbit); break;
    }
  }
}

static bool active() {
  const uint32_t sample = 480;
  int er;

  for (int i = 0; i < 5; i++) {
    AsPcmDataParam pcm;  /* get PCM */
    
    er = pcm.mh.allocSeg(S0_REND_PCM_BUF_POOL, (sample*2*2));
    if (er != ERR_OK) break;
    theOscillator->exec((q15_t*)pcm.mh.getPa(), sample);
    pcm.identifier = 0;
    pcm.callback = 0;
    pcm.bit_length = 16;
    pcm.size = sample*2*2;
    pcm.sample = sample;
    pcm.is_end = false;
    pcm.is_valid = true; 
    er = theMixer->sendData(OutputMixer0, mixer_send_cb, pcm);
    if (er != OUTPUTMIXER_ECODE_OK) {
      Serial.println("OutputMixer send error: " + String(er));
      return false;
    }
  }
  return true;
}

void setup() {
  Serial.begin(115200);
    
  initMemoryPools();
  createStaticPools(MEM_LAYOUT_RECORDINGPLAYER);
  
  theOscillator = new Oscillator();
  theOscillator->begin(SinWave, 1);
  theOscillator->set(0, 0);
  theOscillator->set(0, 700, 200, 50, 400); // attack=0, decay=700, sustain=50, release=400
  theOscillator->lfo(0, 4, 2);
  
  theMixer = OutputMixer::getInstance();
  theMixer->activateBaseband(); 
  theMixer->create();
  theMixer->setRenderingClkMode(OUTPUTMIXER_RNDCLK_NORMAL);
  theMixer->activate(OutputMixer0, HPOutputDevice, mixer_done_cb);
  theMixer->setVolume(-160, 0, 0);
  board_external_amp_mute_control(false);    /* Unmute */

  FFT.begin(WindowRectangle, rec_channel_num, 0);

  theRecorder = MediaRecorder::getInstance();
  theRecorder->begin(rec_attention_cb);
  theRecorder->setCapturingClkMode(MEDIARECORDER_CAPCLK_NORMAL);
  theRecorder->activate(AS_SETRECDR_STS_INPUTDEVICE_MIC, rec_done_cb);
  theRecorder->init(AS_CODECTYPE_LPCM, rec_channel_num
    , rec_sampling_rate, rec_bit_length, rec_bitrate, "/mnt/sd0/BIN");
  theRecorder->setMicGain(ANALOG_MIC_GAIN);
  theRecorder->start();
  // Serial.println("rec wait usec: " + String(rec_wait_usec));
  task_create("audio recording", 120, 1024, audioReadFrames, NULL);
}



/* Actually, sending data */
uint32_t last_time = 0;
void send_signal(uint16_t hz, int repeat = SIGNAL_DURATION) {
  theOscillator->set(0, hz);

#if 0
  int i = 0;
  do {
    active();  
    usleep(ACTIVE_DURATION);
    ++i;
  } while (i < repeat);
#else  
  // interim measures
  active();  
  usleep(ACTIVE_DURATION);
  active();  
  usleep(ACTIVE_DURATION);
#endif

#if 0
  uint32_t cur_time = millis();
  Serial.println(String(cur_time - last_time));
  last_time = cur_time;
#endif
}

void send_char(uint8_t c, int n = SIGNAL_DURATION) {
  send_signal(SPACE, n);  // send start bit
  for (int n = 0; n < 8; ++n, c = c >> 1) { /* LSB fast */
    if (c & 0x01) { /* mark (1) */
      send_signal(MARK, n);
    } else { /* space (0) */
      send_signal(SPACE, n);
    }
  } 
  send_signal(MARK, n);  // send stop bit
}
  
void loop() {
  send_signal(MARK); // default level is mark
  if (Serial.available()) {
    char c = Serial.read();
    send_char(c);
  } 
}




さて、デバッグ出力で動作も念入りに確認し、問題なさそうです。いよいよ入魂の”HELLO WORLD”!





むちゃくちゃ遅いですけど動きましたねー。ちょっと感動です。やっぱり通信ができるって楽しいですね。せっかくなので、SPRESENSEの”HELLO WORLD”の声を録音しておきました。





さて、次はループバックから抜け出していよいよSPRESENSE同士でおしゃべりさせてみようかな。
^^








SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

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




タグ:Modem Spresense
nice!(19)  コメント(0) 
共通テーマ:趣味・カルチャー

二月の電子工作「SPRESENSEで音響通信」ー音響ループバックを試すー [SPRESENSE]

SPRESENSEの音響通信をいよいよ実機で試していきます。前回、データ送受信はFSKで行うことにしました。データ送信はSPRESENSEで2つの音を出力することで実現します。データ受信はSPRESENSEのマイク入力とFFTを使って検波することで実現します。

それらの動作を検証するため、SPRESENSE単体で音をループバックして試してみました。


sDSC02037.jpg



■ SPRESENSEで2つの音を出力する
まずは単体動作での確認です。SPRESENSEで2つの音を出力させるために、TomonobuHayakawa氏作成の「AudioOscillator」を使って試してみます。サンプルコードを参考にスケッチを作成してみました。2つの音は分かりやすくするため「1kHz」と「2kHz」にしました。


#include <OutputMixer.h>
#include <MemoryUtil.h>
#include <arch/board/board.h>
#include <AudioOscillator.h>

#define MARK  (2000)
#define SPACE (1000)

Oscillator  *theOscillator;
OutputMixer *theMixer;

static void mixer_done_cb(MsgQueId id, MsgType type, AsOutputMixDoneParam *p) {
  Serial.println("mixer done callback"); 
  return; 
}

static void mixer_send_cb(int32_t id, bool is_end) { 
  //Serial.println("mixer send callback"); 
  return; 
}

static bool active() {
  const uint32_t sample = 480;
  AsPcmDataParam pcm;
  int er;
  for (int i = 0; i < 5; i++) {
    er = pcm.mh.allocSeg(S0_REND_PCM_BUF_POOL, (sample*2*2));
    if (er != ERR_OK) {
      Serial.println("PCM memory allocate error");
      return false;
    }
    
    theOscillator->exec((q15_t*)pcm.mh.getPa(), sample);
  
    /* Set PCM parameters */
    pcm.identifier = 0;
    pcm.callback = 0;
    pcm.bit_length = 16;
    pcm.size = sample * 2 * 2;
    pcm.sample = sample;
    pcm.is_end = false;
    pcm.is_valid = true;

    /* Send PCM */
    er = theMixer->sendData(OutputMixer0, mixer_send_cb, pcm);
    if (er != OUTPUTMIXER_ECODE_OK) {
      Serial.println("OutputMixer send error: " + String(er));
      return false;
    }
  }
  
  return true;
}


void setup() {
  Serial.begin(115200);

  initMemoryPools();
  createStaticPools(MEM_LAYOUT_PLAYER);
  
  theOscillator = new Oscillator();
  theOscillator->begin(SinWave, 1);
  theOscillator->set(0, 0);
  theOscillator->set(0,700,200,50,400); /* attack=1000,decay=700, sustain=30, release=300 */
  theOscillator->lfo(0, 4, 2);

  theMixer  = OutputMixer::getInstance();
  theMixer->activateBaseband(); 
  theMixer->create();
  theMixer->setRenderingClkMode(OUTPUTMIXER_RNDCLK_NORMAL);
  theMixer->activate(OutputMixer0, HPOutputDevice, mixer_done_cb);
  theMixer->setVolume(-160, 0, 0);
  board_external_amp_mute_control(false); /* Unmute */
  Serial.println("Start Oscillator");
}

void loop() {
 theOscillator->set(0, MARK);
 active();
 usleep(10000); /* 10 msec */

 theOscillator->set(0, SPACE);
 active();
 usleep(10000); /* 10 msec */
}



SPRESENSEの出力を直接PCのマイク入力につなげて音を録音してみました。


FSK_OUT_PCIN.png


PCでキャプチャすると想定通りの波形となっています。


s1K2KFSK.png


PCで録音してみました。未知との遭遇っぽくていいですねぇ~(古いか)






■ SPRESENSEの音を受信してFFTをかける
PCの音声出力をSPRESENSEのマイク入力につないでFFTをかけてみます。FFTはSignalProcessingライブラリのサンプルを参考にして作ってみました。サンプルはマルチコアを使っていますが、ここではメインコアのみで処理しています。

#include <MediaRecorder.h>
#include <MemoryUtil.h>
#include <FFT.h>

#define ANALOG_MIC_GAIN  0 /* +0dB */

MediaRecorder *theRecorder;
bool err_cb = false;

#define FFT_LEN 256
#define CHANNEL_NUM 1

FFTClass FFT;
static float pDst[FFT_LEN];

static const uint32_t rec_bitrate = AS_BITRATE_48000;
static const uint32_t rec_sampling_rate = AS_SAMPLINGRATE_48000;
static const uint8_t  rec_channel_num = CHANNEL_NUM;
static const uint8_t  rec_bit_length = AS_BITLENGTH_16;
static const int32_t buffer_size = FFT_LEN * sizeof(int16_t);
static const uint32_t rec_wait_usec = buffer_size * (1000000 / rec_sampling_rate);
static uint8_t s_buffer[buffer_size*2];

static bool rec_done_cb(AsRecorderEvent e, uint32_t r1, uint32_t r2) {
  return true;
}

static void rec_attention_cb(const ErrorAttentionParam *p) {
  Serial.println("Attention!");  
  if (p->error_code >= AS_ATTENTION_CODE_WARNING)   {
    err_cb = true;
  }
}

void setup() {
  Serial.begin(115200);
    
  initMemoryPools();
  createStaticPools(MEM_LAYOUT_RECORDINGPLAYER);
  
  FFT.begin(WindowRectangle, rec_channel_num, 0);

  theRecorder = MediaRecorder::getInstance();
  theRecorder->begin(rec_attention_cb);
  theRecorder->setCapturingClkMode(MEDIARECORDER_CAPCLK_NORMAL);
  theRecorder->activate(AS_SETRECDR_STS_INPUTDEVICE_MIC, rec_done_cb);
  theRecorder->init(AS_CODECTYPE_LPCM, rec_channel_num
    , rec_sampling_rate, rec_bit_length, rec_bitrate, "/mnt/sd0/BIN");
  theRecorder->setMicGain(ANALOG_MIC_GAIN);
  theRecorder->start();
  Serial.println("Start Recording");
}


void loop() {
  uint32_t read_size;
  int err;

  err = theRecorder->readFrames(s_buffer, buffer_size, &read_size); 
  if (err != MEDIARECORDER_ECODE_OK && err != MEDIARECORDER_ECODE_INSUFFICIENT_BUFFER_AREA) {
    Serial.println("Recording Error");
    theRecorder->stop();
    theRecorder->deactivate();
    theRecorder->end();
    exit(1);
  } 
  
  // Serial.println("buffer_size:" + String(buffer_size) + " read_size:" + String(read_size));
  if (read_size < buffer_size){
    usleep(rec_wait_usec);
    return;  
  }

  FFT.put((q15_t*)s_buffer, FFT_LEN);
  FFT.get(pDst, 0);

  uint32_t index;
  float maxValue;
  int max_line = FFT_LEN/2.56;
  arm_max_f32(pDst, max_line, &maxValue, &index);

  float peakFs = index  * (rec_sampling_rate / FFT_LEN);
  Serial.println(String(peakFs));
}



PCで録音した音の出力をSPRESENSEのマイク入力につなげてFFTで検波できるか試してみます。


PC_OUT_FSKIN.png


出力を見ると、それっぽい値を出しています。ただ矩形の長さが微妙に違うので周波数変調間隔とFFTの受信範囲の調整が必要そうです。(この辺りが難しそう)


s1K2KFSK_RX.png



■ SPRESENSEのループバックで単体送受信を実現
これで送信・受信の単体テストはうまく行ったのでいよいよSPRESENSEの出力とマイク入力をつなぎあわせてループバックさせます。


SPR_LOOPBACK.png


送信、受信のスケッチを合体してみました。録音はスレッドにしてみました。タイミング問題がでるかなと思ったのですが、usleepを使ってうまく動かせたようです。

#include <MediaRecorder.h>
#include <MemoryUtil.h>
#include <AudioOscillator.h>
#include <OutputMixer.h>
#include <arch/board/board.h>
#include <FFT.h>

#define ANALOG_MIC_GAIN  0 /* +0dB */

Oscillator  *theOscillator;
OutputMixer *theMixer;
MediaRecorder *theRecorder;
bool err_cb = false;

#define FFT_LEN 256
#define CHANNEL_NUM 1

#define SPACE (1000)
#define MARK  (2000)

FFTClass FFT;
static float pDst[FFT_LEN];

static const uint32_t rec_bitrate = AS_BITRATE_48000;
static const uint32_t rec_sampling_rate = AS_SAMPLINGRATE_48000;
static const uint8_t  rec_channel_num = CHANNEL_NUM;
static const uint8_t  rec_bit_length = AS_BITLENGTH_16;
static const int32_t buffer_size = FFT_LEN * sizeof(int16_t);
static const uint32_t rec_wait_usec = buffer_size * (1000000 / rec_sampling_rate);
static uint8_t s_buffer[buffer_size*2];

static bool rec_done_cb(AsRecorderEvent e, uint32_t r1, uint32_t r2) {
  // printf("mp cb %x %x %x\n", e, r1, r2);
  return true;
}

static void rec_attention_cb(const ErrorAttentionParam *p) {
  Serial.println("Attention!");  
  if (p->error_code >= AS_ATTENTION_CODE_WARNING)   {
    err_cb = true;
  }
}

static void mixer_done_cb(MsgQueId id, MsgType type, AsOutputMixDoneParam *p) {
  Serial.println("mixer done callback"); 
  return; 
}
static void mixer_send_cb(int32_t id, bool is_end) { 
  // Serial.println("mixer send callback"); 
  return; 
}

static void audioReadFrames() {
  uint32_t read_size;
  int er;
  while (true) {
    er = theRecorder->readFrames(s_buffer, buffer_size, &read_size); 
    if (er != MEDIARECORDER_ECODE_OK && er != MEDIARECORDER_ECODE_INSUFFICIENT_BUFFER_AREA) {
      Serial.println("Recording Error");
      theRecorder->stop();
      theRecorder->deactivate();
      theRecorder->end();
      break;
    } 
    
    // Serial.println("buffer_size:" + String(buffer_size) + " read_size:" + String(read_size));
    if (read_size < buffer_size){
      usleep(rec_wait_usec);
      continue;  
    }

    FFT.put((q15_t*)s_buffer, FFT_LEN);
    FFT.get(pDst, 0);

    uint32_t index;
    float maxValue;
    int max_line = FFT_LEN/2.56;
    arm_max_f32(pDst, max_line, &maxValue, &index);
  
    float peakFs = index  * (rec_sampling_rate / FFT_LEN);
    Serial.println(String(peakFs));
  }
}

static bool active() {
  const uint32_t sample = 480;
  int er;

  for (int i = 0; i < 5; i++) {
    AsPcmDataParam pcm;  /* get PCM */
    
    er = pcm.mh.allocSeg(S0_REND_PCM_BUF_POOL, (sample*2*2));
    if (er != ERR_OK) break;
    theOscillator->exec((q15_t*)pcm.mh.getPa(), sample);
    pcm.identifier = 0;
    pcm.callback = 0;
    pcm.bit_length = 16;
    pcm.size = sample*2*2;
    pcm.sample = sample;
    pcm.is_end = false;
    pcm.is_valid = true; 
    er = theMixer->sendData(OutputMixer0, mixer_send_cb, pcm);
    if (er != OUTPUTMIXER_ECODE_OK) {
      Serial.println("OutputMixer send error: " + String(er));
      return false;
    }
  }
  return true;
}


void setup() {
  Serial.begin(115200);
    
  initMemoryPools();
  createStaticPools(MEM_LAYOUT_RECORDINGPLAYER);
  
  theOscillator = new Oscillator();
  theOscillator->begin(SinWave, 1);
  theOscillator->set(0, 0);
  theOscillator->set(0, 700, 200, 50, 400); // attack=0, decay=700, sustain=50, release=400
  theOscillator->lfo(0, 4, 2);
  
  theMixer = OutputMixer::getInstance();
  theMixer->activateBaseband(); 
  theMixer->create();
  theMixer->setRenderingClkMode(OUTPUTMIXER_RNDCLK_NORMAL);
  theMixer->activate(OutputMixer0, HPOutputDevice, mixer_done_cb);
  theMixer->setVolume(-160, 0, 0);
  board_external_amp_mute_control(false);    /* Unmute */

  FFT.begin(WindowRectangle, rec_channel_num, 0);

  theRecorder = MediaRecorder::getInstance();
  theRecorder->begin(rec_attention_cb);
  theRecorder->setCapturingClkMode(MEDIARECORDER_CAPCLK_NORMAL);
  theRecorder->activate(AS_SETRECDR_STS_INPUTDEVICE_MIC, rec_done_cb);
  theRecorder->init(AS_CODECTYPE_LPCM, rec_channel_num
    , rec_sampling_rate, rec_bit_length, rec_bitrate, "/mnt/sd0/BIN");
  theRecorder->setMicGain(ANALOG_MIC_GAIN);
  theRecorder->start();
  Serial.println("Recording Start!");
  //Serial.println("rec wait usec: " + String(rec_wait_usec));
  task_create("audio recording", 120, 1024, audioReadFrames, NULL);
}


#define DURATION (10000) /* usec */
void loop() {

  theOscillator->set(0, MARK); 
  active();
  usleep(DURATION); 

  theOscillator->set(0, SPACE);
  active();
  usleep(DURATION); 
}




Arduinoのプロッタ出力はこんな感じになります。地味な映像ですけど、ちょっと感動。


FSK4.gif


あとは変調間隔と受信間隔の調整でなんとかデータを送受信ができそうです。
(^^)/~





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

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

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



SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

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



タグ:Modem Spresense
nice!(20)  コメント(0) 
共通テーマ:趣味・カルチャー

二月の電子工作「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” がうまく共存できれば、ループバックで検証ができるところまで行けるかも?

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





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

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

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



SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

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




タグ:Modem Spresense
nice!(23)  コメント(0) 
共通テーマ:趣味・カルチャー

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

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


sDSC01982.jpg


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

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


UART_N8S1.jpg


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





SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

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




タグ:Modem Spresense
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)  コメント(0) 
共通テーマ:趣味・カルチャー
前の5件 | -