SPRESENSEで簡単な信号発生器を作ってみました。材料は以下の通りです。
- SPRESENSE メインボード
- SPRESENSE LTE拡張ボード
- 可変抵抗
LTE拡張ボードは、拡張ボードでもよいと思います。単に小型にしたかっただけですので。
改変抵抗で出力周波数を変更できます。今のところ、ADCの値をそのまま周波数にしているので0~1024Hzまでしか対応していませんが、ここは係数をかけて自由に変更できます。
ソースコードは次の通りです。ADC読み込みに時間がかかっているので、サブコアで値を読み込むようにしています。
メインコア用のコード
#ifdef SUBCORE
#error "Core selection is wrong!!"
#endif
#include <MP.h>
#include <OutputMixer.h>
#include <MemoryUtil.h>
#include <arch/board/board.h>
/* Use CMSIS library */
#define ARM_MATH_CM4
#define __FPU_PRESENT 1U
#include <cmsis/arm_math.h>
#define Q15_SCALE 32768.0f
OutputMixer *theMixer = OutputMixer::getInstance();
const uint32_t sample_num = 480;
const uint32_t sampling_rate = 48000;
static volatile uint32_t output_hz = 440;
const uint32_t subcore = 1;
static void mixer_done_cb(MsgQueId id, MsgType type, AsOutputMixDoneParam *p) { return; }
static void mixer_send_cb(int32_t id, bool is_end) { return; }
void setup() {
Serial.begin(115200);
int er;
initMemoryPools();
createStaticPools(MEM_LAYOUT_RECORDINGPLAYER);
theMixer->activateBaseband();
theMixer->create();
theMixer->setRenderingClkMode(OUTPUTMIXER_RNDCLK_NORMAL);
theMixer->activate(OutputMixer0, HPOutputDevice, mixer_done_cb);
theMixer->setVolume(-30, 0, 0);
board_external_amp_mute_control(false); /* Unmute */
MP.begin(subcore);
MP.RecvTimeout(MP_RECV_POLLING);
}
void loop() {
static int16_t phase_q15 = 0;
int er;
int8_t msgid;
uint32_t val;
int ret = MP.Recv(&msgid, &val, subcore);
if (ret > 0) {
output_hz = val;
}
int16_t phase_inc_q15 = (int16_t)(2.0f * output_hz / sampling_rate * Q15_SCALE);
if (ret > 0) {
MPLog("%d, %d\n", output_hz, phase_inc_q15);
}
AsPcmDataParam pcm;
er = pcm.mh.allocSeg(S0_REND_PCM_BUF_POOL, (sample_num*2*2));
if (er != ERR_OK) return;
q15_t* samples = (q15_t*)pcm.mh.getPa();
for (int n = 0; n < sample_num*2; n+=2) {
q15_t level = arm_sin_q15(phase_q15);
phase_q15 += phase_inc_q15;
if (phase_q15 > 32767) phase_q15 -= 65536;
else if (phase_q15 < -32768) phase_q15 += 65536;
samples[n+0] = level; // L channel
samples[n+1] = level; // R channel
}
pcm.identifier = 0;
pcm.callback = 0;
pcm.bit_length = 16;
pcm.size = sample_num*2*2; // 16bit, 2channel
pcm.sample = sample_num;
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;
}
}
サブコア用のコード
#ifndef SUBCORE
#error "Core selection is wrong!!"
#endif
#include <MP.h>
void setup() {
MP.begin();
}
void loop() {
uint32_t val = analogRead(A2);
int8_t sndid = 100;
//MPLog("%d\n", val);
MP.Send(sndid, val);
usleep(100000);
}
計算をリアルタイムで行っているので処理が間に合うか不安でしたが、特に問題なさそうです。さすがCMSISですね。
音が思ったよりもクリアなので、楽器として使うのもよさそうです。
生成する信号も計算で生成しているので処理さえ間に合えば自由に設計できます。
少ない部品で作れますので、皆さんもぜひ試してみてください。![]()