なおこちらはKickstarter製品で使ったコードの一部です。
setupではI2S関連の初期化と音を出力するためのタスクsound_loopを準備しています。
loopでは割り込み要素として加速度計を使っていて、3軸合成加速度が平均値(ほぼ重力)を一定以上越えたら音を出力するフラグsoundOnを立てます。
sound_loopではループをぐるぐる回って、大きな加速度を検知してsoundOnがonになったら音源番号iSoundを変更して再生位置iを初期化し、内側のループでi2s_push_sampleを使って1音ずつバッファにプッシュし、音を出力します。再生が終わるか、音源番号が変わったらループを抜けます。
内側のループにはウエイトを入れていないですが、DMAバッファがいっぱいになるとi2s_push_sampleで止まるので、大丈夫っぽいです(ポイかよ)。
まぁこんな感じで…。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Sound | |
// | |
#include "driver/i2s.h" | |
#include "driver/gpio.h" | |
#include "ching.h" | |
#include "clap.h" | |
#include "cymbal.h" | |
#include "voice.h" | |
const i2s_port_t I2S_NUM = (i2s_port_t)0; | |
const i2s_bits_per_sample_t BITS_PER_SAMPLE = (i2s_bits_per_sample_t)16; | |
#define ADC_MODE 1 | |
// I2S | |
void set_16k_16() { | |
i2s_config_t i2s_config; | |
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); // Only TX | |
i2s_config.sample_rate = 16000; | |
i2s_config.bits_per_sample = BITS_PER_SAMPLE; | |
i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; //right channels | |
i2s_config.communication_format = I2S_COMM_FORMAT_PCM; | |
i2s_config.dma_buf_count = 4; | |
i2s_config.dma_buf_len = 1024; | |
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1 | |
i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL); | |
} | |
// | |
// 加速度 | |
// | |
#include <Wire.h> // Must include Wire library for I2C | |
#include <SparkFun_MMA8452Q.h> // Includes the SFE_MMA8452Q library | |
MMA8452Q accel(0x1C); | |
volatile int soundOn = false; | |
volatile int iSound = 0; | |
volatile int prevSound = -1; | |
void sound_loop(void *param) { | |
char *buf = NULL; | |
int length; | |
while (1) { | |
if (soundOn == true) { | |
switch (iSound) { | |
case 0: buf = (char*)ching; length = sizeof(ching); break; | |
case 1: buf = (char*)clap; length = sizeof(clap); break; | |
case 2: buf = (char*)cymbal; length = sizeof(cymbal); break; | |
case 3: buf = (char*)voice; length = sizeof(voice); break; | |
default: buf = NULL; iSound = 0; break; | |
} | |
soundOn = false; | |
prevSound = iSound; | |
int i = 0; | |
while (i < length) { | |
int ret = i2s_push_sample(I2S_NUM, &buf[i], portMAX_DELAY); | |
if (ret <= 0) { | |
Serial.print("return = "); | |
Serial.print(ret); | |
delay(1); | |
iSound = -1; | |
break; | |
} else { | |
i += ret; | |
} | |
if (prevSound != iSound || soundOn == true) { | |
break; | |
} | |
} | |
i2s_zero_dma_buffer(I2S_NUM); | |
} else { | |
delay(1); | |
} | |
} | |
} | |
TaskHandle_t task; | |
void setup() | |
{ | |
Serial.begin(115200); | |
Serial.println("MMA8452Q Test Code!"); | |
pinMode(13, OUTPUT); | |
accel.init(); | |
static i2s_pin_config_t pin_config; | |
pin_config.bck_io_num = 26; | |
pin_config.ws_io_num = 25; | |
pin_config.data_out_num = 27; | |
pin_config.data_in_num = -1; //Not used | |
set_16k_16(); | |
i2s_set_pin(I2S_NUM, &pin_config); | |
xTaskCreatePinnedToCore( | |
sound_loop, // Handler | |
"sound_loop", // Task name | |
4096, // Stack size | |
NULL, // Parameter | |
1, // Priority : 25 is the most high | |
&task, // task handler | |
1); // Application CPU | |
Serial.println("setup end"); | |
} | |
float ave = -1; | |
void loop() | |
{ | |
if (accel.available()) | |
{ | |
// First, use accel.read() to read the new variables: | |
accel.read(); | |
float gravity = sqrt(accel.x * accel.x + accel.y * accel.y + accel.z * accel.z); | |
if (ave == -1) { | |
ave = gravity; | |
} else { | |
if (abs(gravity - ave) > 1000) { | |
digitalWrite(13, HIGH); | |
soundOn = true; | |
delay(25); | |
digitalWrite(13, LOW); | |
} else { | |
digitalWrite(13, LOW); | |
} | |
ave = (ave * 9.0 + gravity) / 10.0; | |
} | |
Serial.print("gravity="); | |
Serial.print(gravity, 3); | |
Serial.print(", ave="); | |
Serial.print(ave, 3); | |
Serial.print(", abs="); | |
Serial.print(abs(gravity - ave)); | |
Serial.print(" "); | |
printCalculatedAccels(); | |
Serial.println(); // Print new line every time. | |
delay(1); | |
} else { | |
Serial.println("not available"); | |
delay(1000); | |
} | |
} | |
void printCalculatedAccels() | |
{ | |
Serial.print(accel.cx, 3); | |
Serial.print("\t"); | |
Serial.print(accel.cy, 3); | |
Serial.print("\t"); | |
Serial.print(accel.cz, 3); | |
Serial.print("\t"); | |
} |
いつも興味深く拝見しています。
返信削除ESP32で内蔵DAC+i2sで音を出そうと四苦八苦しているのですが、
出てくるのは雑音ばかりです。
「データは符号付き16ビット整数をみっちり詰め込んで」の
意味が分からず先に進めないでいます。
例えば上記のコード中の、ching.hなどは、
signed 16bit変換したraw pcmがC言語配列初期化の形式で
書かれていると推察されますが、このファイル
どこかで公開していただけないでしょうか・・・
ご拝読ありがとうございます。
削除ching.hは音楽家さんが作成したデータが元なので公開は難しいですが、先頭はこんな感じです。
const uint16_t ching[] = {
// file length = 60044
// id=RIFFÑÍ, file size=60036, wave=WAVEfmt , chunkSize=16, formatId=1,
// ch=1, samples per sec=16000, bytes per sec=32000, bits per sample=16
// sizeofformat=36, buf=data`Í, id=data`Í, data size=60000
// lines=1875
0x02d2,0xf6f9,0x09f8,0x02a2,0xfab6,0xfa03,0x0787,0x034e,0xf6f4,0x16d4,0xf5a0,0xe372,0x0e55,0xfcf5,0xfb2f,0x2224,
0xfc30,0xee70,0x0df2,0xfe7e,0xde21,0x0c9e,0x158a,0xe082,0x100c,0x17dc,0xe705,0x0c68,0x0be5,0xde63,0x0b46,0x0d16,
なお、上のソース、宣言は符号なしですが、中に入っているデータは符号付き16ビット整数です。最初音が出るまで苦労しました。とりあえず最初はsin関数などで正弦波を作って鳴らしてみると検証しやすいです。いきなり音を出すと回路的なノイズなのかデータが間違っているのか判断しにくいですが、正弦波なら耳で聞いたりオシロで波形を見れば何が問題なのかわかりやすいので。計算で作ったデータで正しい音が出るようになったら、raw pcmの方でも正弦波か正弦波っぽい音を録音して変換して試すと、切り分けが容易になるかと思います。
雑音になってしまう理由として、他にはraw pcmのデータがLRLRLR…と並んでいるのに片チャンネルだけ出力しているケースもありました。
// I2S
void set_16k_16() {
i2s_config_t i2s_config;
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); // Only TX
i2s_config.sample_rate = 16000;
i2s_config.bits_per_sample = BITS_PER_SAMPLE;
i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; //right channels
i2s_config.communication_format = I2S_COMM_FORMAT_PCM;
i2s_config.dma_buf_count = 4;
i2s_config.dma_buf_len = 1024;
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
}
ここのchannnel_formatを変えてみてください。
sinのデータ、残っていたのでgistにあげてみました。
削除ご覧のようにLRのデータです。たしか右と左で周波数の違う正弦波が入っていたと思います。こっちのデータはint16_tですが、中に入っているデータが同じなら関係ないです。元データはwavで単に読んだデータをそのままhexで変換して書き出しているだけです。
https://gist.github.com/TareObjects/7ca5a9ee903ddb6c3e6eb504c8b9f1bd