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
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で検波できるか試してみます。
出力を見ると、それっぽい値を出しています。ただ矩形の長さが微妙に違うので周波数変調間隔と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)
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のプロッタ出力はこんな感じになります。地味な映像ですけど、ちょっと感動。
あとは変調間隔と受信間隔の調整でなんとかデータを送受信ができそうです。
(^^)/~
関連リンク
二月の電子工作「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