SSブログ

二月の電子工作「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


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



関連リンク
二月の電子工作「SPRESENSEで音響通信」
https://makers-with-myson.blog.ss-blog.jp/2021-02-02
二月の電子工作「SPRESENSEで音響通信」ー送受信の検討ー
https://makers-with-myson.blog.ss-blog.jp/2021-02-07
二月の電子工作「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!(20)  コメント(0) 
共通テーマ:趣味・カルチャー

nice! 20

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

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