二月の電子工作「SPRESENSEで音響通信」ー音響ループバックを試すー [SPRESENSE]
SPRESENSEの音響通信をいよいよ実機で試していきます。前回、データ送受信はFSKで行うことにしました。データ送信はSPRESENSEで2つの音を出力することで実現します。データ受信はSPRESENSEのマイク入力とFFTを使って検波することで実現します。
それらの動作を検証するため、SPRESENSE単体で音をループバックして試してみました。
■ SPRESENSEで2つの音を出力する
まずは単体動作での確認です。SPRESENSEで2つの音を出力させるために、TomonobuHayakawa氏作成の「AudioOscillator」を使って試してみます。サンプルコードを参考にスケッチを作成してみました。2つの音は分かりやすくするため「1kHz」と「2kHz」にしました。
SPRESENSEの出力を直接PCのマイク入力につなげて音を録音してみました。
PCでキャプチャすると想定通りの波形となっています。
PCで録音してみました。未知との遭遇っぽくていいですねぇ~(古いか)
■ SPRESENSEの音を受信してFFTをかける
PCの音声出力をSPRESENSEのマイク入力につないでFFTをかけてみます。FFTはSignalProcessingライブラリのサンプルを参考にして作ってみました。サンプルはマルチコアを使っていますが、ここではメインコアのみで処理しています。
PCで録音した音の出力をSPRESENSEのマイク入力につなげてFFTで検波できるか試してみます。
出力を見ると、それっぽい値を出しています。ただ矩形の長さが微妙に違うので周波数変調間隔とFFTの受信範囲の調整が必要そうです。(この辺りが難しそう)
■ SPRESENSEのループバックで単体送受信を実現
これで送信・受信の単体テストはうまく行ったのでいよいよSPRESENSEの出力とマイク入力をつなぎあわせてループバックさせます。
送信、受信のスケッチを合体してみました。録音はスレッドにしてみました。タイミング問題がでるかなと思ったのですが、usleepを使ってうまく動かせたようです。
Arduinoのプロッタ出力はこんな感じになります。地味な映像ですけど、ちょっと感動。
あとは変調間隔と受信間隔の調整でなんとかデータを送受信ができそうです。
(^^)/~
関連リンク
二月の電子工作「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
それらの動作を検証するため、SPRESENSE単体で音をループバックして試してみました。
■ 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のマイク入力につなげて音を録音してみました。
PCでキャプチャすると想定通りの波形となっています。
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 FFTClassFFT; 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で検波できるか試してみます。
出力を見ると、それっぽい値を出しています。ただ矩形の長さが微妙に違うので周波数変調間隔とFFTの受信範囲の調整が必要そうです。(この辺りが難しそう)
■ SPRESENSEのループバックで単体送受信を実現
これで送信・受信の単体テストはうまく行ったのでいよいよSPRESENSEの出力とマイク入力をつなぎあわせてループバックさせます。
送信、受信のスケッチを合体してみました。録音はスレッドにしてみました。タイミング問題がでるかなと思ったのですが、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) FFTClassFFT; 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のプロッタ出力はこんな感じになります。地味な映像ですけど、ちょっと感動。
あとは変調間隔と受信間隔の調整でなんとかデータを送受信ができそうです。
(^^)/~
関連リンク
二月の電子工作「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
SONY SPRESENSE メインボード CXD5602PWBMAIN1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware
SONY SPRESENSE 拡張ボード CXD5602PWBEXT1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware
コメント 0