🔉

Teensy 4.1からI2Sで音声出力をする(PCM5102A)

2025/01/14に公開

Teensy 4.1について

Teensy 4.1はARM Cortex-M7を搭載した高性能なマイコンです。
Cortex-M7にはFPUが搭載されており、ESP32よりも高速で浮動小数点演算ができます。
定格600MHzで動作しオーバークロックも可能、マイクロSDカードスロット付きでI/Oピン数は55とこれひとつで様々な物が作れそうです。
https://www.pjrc.com/store/teensy41.html

公式でオーディオライブラリが提供されている

Teensyでは音声データの再生や合成、録音、フィルタリングやミキシング等の機能を備えたオーディオツールキットを提供しています。
Teensyのオーディオライブラリはかなり豊富な内容で、オーディオの入出力だけではなく、シンセサイザー用の機能やコーラス、リバーブ、グラニュラー等のエフェクト機能もあり、このライブラリだけでシンセサイザーを作ることもできてしまいます。
ビット深度は16bit、サンプルレートは44.1kHzでオーディオのストリーミングが可能です。
Audio System Design Toolで視覚的にオーディオシステムの設計をすることができます。
https://www.pjrc.com/teensy/td_libs_Audio.html

I2SDACを使って音声出力

TeensyのオーディオライブラリではAudioPlayQueueを使って音声の再生ができます。
AudioPlayQueueに自身で用意したオーディオデータを渡してI2S通信をします。

配線

Teensy 4.1にPCM5102Aを接続します。
割り当て表を参考に各ピンを接続してください。
PCM5102AはMCLKが不要なので、LRCLK、BCLK、SDATAを接続します。
※PCM5102Aの動作に必要な回路は構築済みとします。

Teensy 4.1 PCM5102A
OUT2 (ピン番号2) DIN
LRCLK2 (ピン番号3) LRCK
BCLK2 (ピン番号4) BCK

コード

今回は以下のようなコードを書きました。

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

AudioPlayQueue  queue_L, queue_R;
AudioOutputI2S2 i2s2;
AudioConnection patchCord1(queue_R, 0, i2s2, 1);
AudioConnection patchCord2(queue_L, 0, i2s2, 0);

#define SAMPLE_RATE 44100.0
#define BUFFER_SIZE 128

float phase = 0;
float delta = 440.0 / SAMPLE_RATE;

int16_t samples_L[BUFFER_SIZE];
int16_t samples_R[BUFFER_SIZE];

int16_t triangle(float phase) {
    return static_cast<int16_t>((1.0 - fabs(2.0 * phase - 1.0)) * 32767);
}

void update() {
    for(size_t i = 0; i < BLOCK_SIZE; i++) {
        samples_L[i] = triangle(phase);
        samples_R[i] = triangle(phase);
        phase += delta;
        if (phase >= 1.0) phase -= 1.0;
    }
    queue_L.play(samples_L, BUFFER_SIZE);
    queue_R.play(samples_R, BUFFER_SIZE);
}

void setup() {
    queue_L.setMaxBuffers(2);
    queue_R.setMaxBuffers(2);
    AudioMemory(4+2);
}

void loop() {
    if(queue_L.available() && queue_R.available()) {
        update();
    }
}

オーディオの設定は以下の部分で行われています。

AudioPlayQueue  queue_L, queue_R;
AudioOutputI2S2 i2s2;
AudioConnection patchCord1(queue_R, 0, i2s2, 1);
AudioConnection patchCord2(queue_L, 0, i2s2, 0);

AudioPlayQueueはステレオを想定して左チャンネルと右チャンネル用で2つ用意しました。
AudioOutputI2S2は、I2S通信時にOUT2,LRCLK2,BCLK2,IN2,MCLK2が割り当てられたピンを使用します。OUT1,LRCLK1...のピンを使いたい場合はAudioOutputI2Sを使用します。
AudioConnectionで各オブジェクトの接続をしています。

メモリの割り当て

void setup() {
    queue_L.setMaxBuffers(2);
    queue_R.setMaxBuffers(2);
    AudioMemory(4+2);
}

オーディオライブラリでは128byte(約2.9ms)を1ブロックとしてメモリを確保します。
setMaxBuffers()で、キューのバッファサイズを設定します。
Teensy 4.1の場合はデフォルトで80ブロック割り当てられますが、シンセサイザーとして使う場合はバッファが多すぎるため、今回は最小値の2を設定しています。
AudioMemory()は、オーディオライブラリ全体で使用するメモリ量を設定します。
今回はAudioPlayQueueが2つあり、バッファサイズがそれぞれ2ブロックなので4ブロックは確定で使用します。
少し余裕を持たせて、6ブロック分設定します。

メモリが足りないと感じたら、200ブロックなど大きな数値を割り当ててから、AudioMemoryUsageMax();を使用して、最適なメモリ量を探すこともできます。
https://forum.pjrc.com/index.php?threads/audiomemory-what-parameter-should-i-pass.39245/

オーディオの再生

queue_L.play(samples_L, BUFFER_SIZE);
queue_R.play(samples_R, BUFFER_SIZE);

play()でサンプルデータとサンプル数を渡して再生します。

実行してみる

プログラムを実行した結果、無事に三角波が出力されました。

Discussion