0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Teensy4.1 + ESP32 + PCM5102Aボード その他

Last updated at Posted at 2025-02-28

概要

Teensy4.1 + ESP32 + PCM5102Aボードを使って、Bluetooth Audioレシーバー付きのシンセサイザーを作るテスト。目的はBluetoothでオケを流しながらウインドシンセが吹ける音源モジュール。

IMG_8275.jpeg

IMG_8304.jpeg

電源はUSBから。この組み合わせだと、TeensyのUSBポートを使ってもESP32のUSBポートを使っても使うことができる。普段使う分にはどうでもいいが、開発中はとても便利。普段使いの時はTeensyの方を使った方がUSB接続音源モジュールとしても使えるのでちょっといいかもしれない。

使ってるもの

接続

Teensy 4.1
card11a_rev4_web.jpg
ESP32
doc-esp32-pinout-reference-wroom-devkit.png
PCM5102Aボード
Unknown.jpeg

Teensy4.1はI2Sのマスターとスレーブどちらでも動作するが、ESP32はマスターでしか動かないので、ESP32をマスターとする。すべてのボードの5V、GND、BCLK、LRCLKをそれぞれ接続し、ESP32のDATA OUTをTeensy4.1のDATA INに、Teensy4.1のDATA OUTをPCM5102AのDATA INに接続する。

ESP32はBLE-MIDIも動かして、受けたデータをそのままTeensyに受け流し、Teensyが受けたものをESP32に受け流す。よってこれらのSerialはMIDIのbps(31250)で使う。Teensy側はMIDIとしてピンを使うが、ESP32側は単にSerialとして使う(なんでそうしたんだったか忘れた)。もしかしたらもっと高いレートでやってしまってもいいのかもしれない(未確認)。

ESP32 → Teensy4.1

GPIO26 → 21 (BCLK1)
GPIO25 → 20 (LRCLK1)
GPIO22 → 8 (IN1)
GPIO36 (RXD2として) → 17 (TX4)
GPIO18 (TXD2として) → 16 (RX4)
5V → Vin
GND → GND

GPIO26,25,22 を使っているのは、以前M5Stamp Picoで使っていた時にそうしていたから。

Teensy4.1 → PCM5102A

21 (BCLK1) → BCK
20 (LRCLK1) → LCK
7 (OUT1A) → DIN
GND → SCK
GND → GND
5V → VIN

Teensy4.1 → SSD1306(SPI)

GND → GND
5V → VCC
14 → D0
11 → D1
15 → RES
9 → DC
10 → CS

Teensy4.1 → タクトスイッチ

2 → SW1
3 → SW2
4 → SW3
5 → SW4

プログラム

Arduinoを使う。ESP32とTeensy4.1はボードマネージャーで追加して、ESP32-A2DP をライブラリに追加しておく。

ESP32

File -> Examples -> ESP32-A2DP にある bt_music_receiver_32bits にピンアサイン変更を加えただけ。

#include "AudioTools.h"
#include "BluetoothA2DPSink.h"

AudioInfo info(44100, 2, 32);
I2SStream out;
NumberFormatConverterStream convert(out);
BluetoothA2DPSink a2dp_sink(convert);

void setup() {
  Serial.begin(115200);
  // AudioToolsLogger.begin(Serial, AudioLogger::Info);

  // Configure i2s to use 32 bits
  auto cfg = out.defaultConfig();
  cfg.copyFrom(info);
  cfg.pin_bck = 26;
  cfg.pin_ws = 25;
  cfg.pin_data = 22;
  out.begin(cfg);

  // Convert from 16 to 32 bits
  convert.begin(16, 32);

  // start a2dp
  a2dp_sink.start("AudioKit");  
}


void loop() {
  delay(1000); // do nothing
}

Teensy4.1

Audio Design Toolで下図のように組んでExportしたものを流用する。このツールは便利で、簡単なシンセサイザーくらいはこのツールでデザインできる。ここではテストのために単にサイン波を生成してI2Sの入力に入ってきたものとミキサーでミックスしてI2Sに出力する。sgtl1500はTeensy Audio Board用だが、これがPCM5102Aを使う際にも使えるっぽいので置いておく。
image.png

エクスポートしたコードスニペットを使ってプログラムを書く。

#define SLAVE_MODE 1

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

AudioSynthWaveformSine   sine1;

#if SLAVE_MODE
AudioInputI2Sslave       i2sInputs;
AudioOutputI2Sslave      i2sOutputs;
#else
AudioInputI2S            i2sInputs;
AudioOutputI2S           i2sOutputs;
#endif
AudioMixer4              mixerL;
AudioMixer4              mixerR;

AudioConnection          patchCord1(sine1,              0, mixerL,             0);
AudioConnection          patchCord2(sine1,              0, mixerR,             0);
AudioConnection          patchCord3(i2sInputs,          0, mixerL,             1);
AudioConnection          patchCord4(i2sInputs,          1, mixerR,             1);
AudioConnection          patchCord5(mixerR,             0, i2sOutputs,         1);
AudioConnection          patchCord6(mixerL,             0, i2sOutputs,         0);

AudioControlSGTL5000     sgtl5000_1;

void setup() {
    AudioMemory(20);
    sgtl5000_1.enable();
    sgtl5000_1.volume(0.5); // これは実はPCM5102Aボードでは効果がない

    sine1.amplitude(0.5);
    sine1.frequency(1000);

}

void loop() {
    delay(1000);
}

OLED

小さくて安価な単色OLEDディスプレイにはI2C版とSPI版の2種類があるが、波形表示などをするにはSPI版でないと速度的に厳しいのでSPI版を使っている。

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_MOSI  11   // = DOUT = Display SDA
#define OLED_CLK   14   // = SCK  = Display SCL
#define OLED_DC     9   //        = Display DC
#define OLED_CS    10   //        = Display CS
#define OLED_RESET 15   //        = Display RES
Adafruit_SSD1306 display(128, 64, OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

void initDisplay()
{
    if (!display.begin(SSD1306_SWITCHCAPVCC)) {
        Serial.println(F("SSD1306 allocation failed"));
        for(;;); // Don't proceed, loop forever
    }
  
    // ディスプレイをクリア
    display.clearDisplay();

    display.setTextSize(1);  // 出力する文字の大きさ

    display.setTextColor(WHITE);  // 出力する文字の色

    display.setCursor(0, 0);  // 文字の一番端の位置

    display.println("STUDIO-R");    // 出力する文字列
    display.println("TWI2000");

    // ディスプレイへの表示
    display.display();
}

BLE-MIDI

Teensy4.1にないBluetoothの機能はESP32が担う。BLE-MIDIを使ってESP32が送受信を行うが、データはすべてTeensyが処理を行う。

ESP32 → Teensy4.1への送信
Core0で行う場合は単に送ればいいが、負荷分散のためにCore1で行う場合はちょっと複雑になる。

void setup()
{
    // Serial (USB) for debug output
    Serial.begin (115200);

    // Serial2 をTeensyとのMIDIデータ送受信に使う。Teensy側がMIDIクラスでSerialを使うので速度を合わせている。
    // SERIAL_8N1 の意味
	//   8 : データビット数(8ビット)
	//   N : パリティ(None = なし)
	//   1 : ストップビット数(1ビット)
    Serial2.begin (31250, SERIAL_8N1, RXD2, TXD2);

    a2dp_sink.start ("TWV2000M");  // これはBluetooth Audio

    // BLE-MIDIの初期化
    BLEMidiServer.begin ("TWV2000M");
    BLEMidiServer.setOnConnectCallback ([](){
        Serial.println ("BLE-MIDI Connected");
    });
    BLEMidiServer.setOnDisconnectCallback([](){
        Serial.println ("BLE-MIDI Disconnected");
    });
    BLEMidiServer.setNoteOnCallback (onNoteOn);
    BLEMidiServer.setNoteOffCallback (onNoteOff);
    BLEMidiServer.setControlChangeCallback (onControlChange);
    //BLEMidiServer.setSystemExclusiveCallback (onSysex); // SysEx使うなら…

    // Core1で処理を行う場合は Queue を作成する
#if USE_CORE1_TO_SEND_MIDI
    xQueue = xQueueCreate (50, sizeof(int32_t));
    if(xQueue != NULL)
    {
        xTaskCreatePinnedToCore (MidiTask, "MidiTask", 4096, NULL, 1, NULL, 1);
    }
    else
    {
        Serial.println ("Error: could not create queue.");
    }
#endif
    
    debugOutput("End setup");
}

#if USE_CORE1_TO_SEND_MIDI
void MidiTask (void* arg)
{
    BaseType_t xStatus;
    int32_t receivedValue = 0;
    const TickType_t xTicksToWait = 1U; // [ms]

    while (1)
    {
        xStatus = xQueueReceive(xQueue, &receivedValue, xTicksToWait);        
        if(xStatus == pdPASS) // receive error check
        {
            Serial.print ("received data : ");
            Serial.println (receivedValue);

            uint8_t data[3];
            data[0] = (receivedValue >> 16) & 0xff;
            data[1] = (receivedValue >> 8) & 0xff;
            data[2] = receivedValue & 0xff;
            Serial2.write(data, 3); // 2バイトメッセージや1バイトメッセージを考慮してないじゃん…
        }
        else
        {
             if (uxQueueMessagesWaiting(xQueue) != 0)
             {
                 while(1)
                 {
                     Serial.println("rtos queue receive error, stopped");
                     delay (1000);
                 }
             }
        }
    }
}
#endif
void sendMidiMessage (uint8_t status, uint8_t data1, uint8_t data2)
{
#if USE_CORE1_TO_SEND_MIDI
    BaseType_t xStatus;
    int32_t sendValue = (status << 16) |
                        (data1 << 8) |
                        data2;
    xStatus = xQueueSend(xQueue, &sendValue, 0);
    if (xStatus != pdPASS)
    {
        Serial.println("Error: Could not send data to core1");
    }
#else
    uint8_t data[3];
    data[0] = status;
    data[1] = data1;
    data[2] = data2;
    Serial2.write (data, 3);
#endif
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?