MAX98357 & PCM5102
2023.01.16
YouTube でも紹介しています。画像をクリックすると再生できます。
以前に、ラズベリーパイからI2S接続により、デジタルオーディオ変換モジュール(DAC)PCM5101を介してし、ハイレゾ音源の再生を行いました。
I2S通信によるハイレゾ音源再生
今回は、ESP32からI2S接続により、MAX98357AあるいはPCM5102Aを用いて、WAV音源の再生を行います。
MAX98357Aはアンプ内蔵のモノラルDAC、PCM5102AはステレオDACですが別途アンプを必要とします。
いずれも5W未満の小さなスピーカーを駆動する際に有用です。
WAV音源再生にあたっては、配線の違いだけで、MAX98357A、PCM5102Aともに同じコードが使用できます。
■Adafruit QT Py ESP32-S2 WiFi Dev Board with STEMMA QT
使用するマイコンボードです。
●ESP32-S2 240MHz
●4 MB Flash & 2 MB PSRAM
●2.4 GHz Wi-Fi (SoC)
●Two I2C ports
●Hardware UART
●Hardware SPI
●Hardware I2S on any pins
●3.3V regulator with 600mA peak output
Ref.Adafruit QT Py ESP32-S2
Qt Py Pinout
QT Py ESP32-S2 の詳細は下記をご覧ください。
Adafruit QT Py + XIAO Expansion board
■SPI Flash SD / SD CARD MODULE
音源はSPI Flash SDあるいはSDカードから読みだして再生します。
Adafruit SPI Flash SD Card
容量:4Gbit(512MByte)
最大 50Mhz のクロック周波数をサポート
組み込みの HW ECC エンジンと信頼性の高い NAND 管理メカニズム
クラス8までの書き込み速度
SPI Flash SDの詳細は下記を参考にしてください。
FTP Server & SPI Flash SD
■MAX98357A搭載 I2S 3W D級アンプボード

・ピンでゲインを3 dB、6 dB、9 dB、12 dB、15 dBの5段階に選択可能
・過熱シャットダウン機能
・出力:3.2 W(4 Ω時) / 1.8 W(8 Ω、5 V電源電圧時)
・I²Sサンプルレート:8-96 kHz
・PSRR: 77 dB typ @ 1 KHz
・MCLK不要
・クリック/ポップを低減
●GAIN
音量はGAINとVIN端子あるいはGND端子の間に抵抗を挟むことで5段階の調整が可能です。
・3dB(GAIN→100KΩ→VIN)
・6dB(GAIN→VIN)
・9dB(未接続:default)
・12dB(GAIN→GND)
・15dB(GAIN→100KΩ→GND)
●SD / MODE
SD(MODE)端子に掛ける電圧で、アンプの停止あるいは出力制御を行います。
・アンプ停止(SD→GND)
・ステレオ音源左右の平均をモノラル出力(Vin→(0.16V~0.77V)→SD)
・右チャンネルのみを出力(Vin→(0.77V~1.4V)→SD)
・左チャンネルのみを出力(Vin→(1.4V~)→SD)
SD端子は100KΩの内部プルダウン抵抗を有しているので、VinとSD端子の間に挟む抵抗との分圧を計算します。
Adafruit MAX98357 I2S Class-D Mono Amp / Pinouts
●配線
上記の設定をもとにした配線です
| FlashSD/microSD | - | ESP32-S2 | - | MAX98357 |
| | - | A0 | - | BCLK |
| | - | A1 | - | LRC |
| | - | A2 | - | DIN |
| CS | - | A3 | | |
| (RasPi)RX | - | TX | | |
| (RasPi)TX | - | RX | | |
| SCK | - | SCK | | |
| MISO | - | MISO | | |
| MOSI | - | MOSI | | |
| Vin | - | 3.3V | -100KΩ- -1MΩ- - | GAIN SD Vin |
| GND | - | GND | - | GND |

画面中央にある2枚のモジュールは下が SPI FLASH SD、上が microSDカードモジュールです。チップセレクトピンを差し替えてどちらでも使えるようにしてあります。
8Ω1.5Wのスピーカーに、ヘッダーピンをはんだ付けしてブレッドボードに挿しています。
●ノイズ対策
ローパスフィルタ回路を組込みました。100μHのマイクロインダクタと220pFのキャパシタを使っています。
これでかなりノイズが軽減されました。

貧相なスピーカーを使っているので、GAIN端子とVDDの間に100KΩの抵抗を挟んで3dBに設定しています。
また、ステレオ音源を使用するので、SD端子とVDDの間に1MΩの抵抗を設置して、左右の平均をモノラルで出力する設定にしています。

AliExpress - MAX98357
ローパスフィルタ(LPF)

直流や低周波の信号を通過させ、高周波の信号をカットするフィルタ回路です。主に高周波ノイズのカットに使用されます。
またオーディオでは低音用スピーカーの高音/中音成分カットに使用されます。
インダクタは高周波になるほど高いインピーダンスを持ちます。
このため、高周波電流を阻止するローパスフィルタとして働き、高周波ノイズを減衰させることができます。
インダクタに電流が流れると磁束が発生して電流のエネルギーが磁気のエネルギーに変化されますが、電流の変化に伴ってこの磁束は再び電磁誘導によって電流に変換されます。このとき、磁束のエネルギーのすべてが電流のエネルギーに戻るのではなく、一部は磁気損失として失われます。
LCフィルタの種類とローパスフィルタにおける部品選定事例
■音楽CDのリッピング
音楽CDリッピングとは音楽CDに入っている音声データを読み取って音楽ファイルに変換することをいいます。
Windows Media Player を使って簡単にリッピングできます。ここでは、WAVファイルとして書き出しています。

ここで取得したWAVファイルを、SPI FLASH SDに保存して使用します。
■I2S設定
I2S設定に関しては、「よみやチャンネル」さんで詳しく説明されていますので、そちらをご覧ください。

高性能DAC PCM5102をESP32で使ってみる
I2Sヘッダファイルの内容は下記をご覧ください。
esp32_MusicPlayer
I2S設定の中で下記の赤文字の部分を、音源であるWAVファイルのヘッダー情報を解析して動的に設定しています。
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = sampleRate,
.bits_per_sample = i2s_bitPerSample,
.channel_format = i2s_channelFormat,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S | I2S_COMM_FORMAT_STAND_MSB),
.intr_alloc_flags = 0,
.dma_buf_count = I2S_BUFFER_COUNT,
.dma_buf_len = I2S_BUFFER_SIZE,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0
};
~
i2s_set_clk((i2s_port_t)I2S_CH_NUM, sampleRate, (i2s_bits_per_sample_t)bitPerSample, (i2s_channel_t)numChannels);
もっともシンプルなWAVファイルは下記の通りです。バイナリ・エディタでWAVファイルを開いています。

1番目のサブチャンクIDが「fmt 」、2番目のサブチャンクIDが「data」です。
Windows Media PlayerでリッピングしたWAVファイルにはLISTという名前の楽曲情報が格納されているサブチャンクが存在します。
さらに、WAV書式が拡張されていて、PCM形式のWAVファイルでは必要のないcbSize(赤枠部分)という属性が追加されています。

WAVEFORMATEX 構造体
Arduino言語はいわばC言語なので、構造体を使って、簡単にヘッダー情報の解析が可能です。
また、ヘッダー情報中の数値はリトルエンディアンの並びで、ESP32もリトルエンディアンなので、そのまま使用できます。
ラズベリーパイにインストールしたPlatformIOのデバイスモニターでESP32とシリアル通信してヘッダー情報の解析結果を表示してみました。

ここで取得したチャンネル数(NumChannels)、サンプリング周波数(SampleRate)、解像度(BitPerSample)を、先ほどのi2s設定に反映させます。
また、音源の演奏データサイズは、Subchunk2Sizeになります。
WAVファイルのヘッダー情報解析に関しては、下記も参考にしてください。
音声ファイルの切貼り
■プログラミング
I2Sの設定さえしてしまえば、あとは簡単です。
I2S_Init(pwf->SampleRate,pwf->BitPerSample, pwf->NumChannels);
long len = pwf->Subchunk2Size;
while(len>0) {
if (len>I2S_BUFFER_SIZE) {
file.readBytes(i2s_buf, I2S_BUFFER_SIZE);
I2S_Write(i2s_buf, I2S_BUFFER_SIZE);
} else {
file.readBytes(i2s_buf, len);
I2S_Write(i2s_buf, len);
break;
}
len -= I2S_BUFFER_SIZE;
}
演奏データをI2Sバッファサイズ単位でMAX98357に流し込んで演奏させています。
●ソースコード
2週間ごとに動画作成と解説記事を作成しているので、ソースコードはかなり雑です。適当に編集して使ってください。
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
#include "esp_system.h"
#define I2S_OUT_BCK A0
#define I2S_OUT_LCK A1
#define I2S_OUT_DIN A2
#define I2S_CH_NUM I2S_NUM_0
#define I2S_BUFFER_COUNT 4
#define I2S_BUFFER_SIZE 512
char i2s_buf[I2S_BUFFER_SIZE];
#define SD_CS A3
#define SDSPEED 40000000
// The Canonical WAVE file format
typedef struct {
uint8_t ChunkID[4];
uint32_t ChunkSize;
uint8_t Format[4];
uint8_t Subchunk1ID[4];
uint32_t Subchunk1Size;
uint16_t AudioFormat;
uint16_t NumChannels;
uint32_t SampleRate;
uint32_t ByteRate;
uint16_t BlockAlign;
uint16_t BitPerSample;
uint8_t Subchunk2ID[4];
uint32_t Subchunk2Size;
uint8_t *wavdata;
} WAV_FORMAT;
File file;
size_t i2s_bytes_write = 0;
void I2S_Init(uint32_t sampleRate, uint16_t bitPerSample, uint16_t numChannels) {
i2s_bits_per_sample_t i2s_bitPerSample;
i2s_channel_fmt_t i2s_channelFormat;
switch(bitPerSample) {
case 8: i2s_bitPerSample = I2S_BITS_PER_SAMPLE_8BIT; break;
case 16: i2s_bitPerSample = I2S_BITS_PER_SAMPLE_16BIT; break;
case 24: i2s_bitPerSample = I2S_BITS_PER_SAMPLE_24BIT; break;
case 32: i2s_bitPerSample = I2S_BITS_PER_SAMPLE_32BIT; break;
}
switch(numChannels) {
case 1: i2s_channelFormat = I2S_CHANNEL_FMT_ONLY_LEFT; break;
case 2: i2s_channelFormat = I2S_CHANNEL_FMT_RIGHT_LEFT; break;
}
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = sampleRate,
.bits_per_sample = i2s_bitPerSample,
.channel_format = i2s_channelFormat,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S | I2S_COMM_FORMAT_STAND_MSB),
.intr_alloc_flags = 0,
.dma_buf_count = I2S_BUFFER_COUNT,
.dma_buf_len = I2S_BUFFER_SIZE,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0
};
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_OUT_BCK,
.ws_io_num = I2S_OUT_LCK,
.data_out_num = I2S_OUT_DIN,
.data_in_num = -1 //Not used
};
i2s_driver_install((i2s_port_t)I2S_CH_NUM, &i2s_config, 0, NULL);
i2s_set_pin((i2s_port_t)I2S_CH_NUM, &pin_config);
i2s_set_clk((i2s_port_t)I2S_CH_NUM, sampleRate, (i2s_bits_per_sample_t)bitPerSample, (i2s_channel_t)numChannels);
}
void I2S_Write(char* data, int numData) {
i2s_write((i2s_port_t)0, (const char *)data, numData, &i2s_bytes_write, portMAX_DELAY);
}
boolean getWavFormat(WAV_FORMAT *pwf) {
long len;
short cbsize;
if (file.read((uint8_t *)pwf,20) == -1) return false;
if (memcmp(pwf->Subchunk1ID,"fmt ",4)==0) {
if((len = file.read((uint8_t *)pwf+20,24))==-1) return false;
} else if (memcmp(pwf->Subchunk1ID,"LIST",4)==0) {
if (file.seek(20+pwf->Subchunk1Size)) {
if((len = file.read((uint8_t *)pwf+12,24))==-1) return false;
if (pwf->Subchunk1Size == 18) {
// skip cbsize on WAVEFORMATEX
if((len = file.read((uint8_t *)&cbsize,2))==-1) return false;
}
if((len = file.read((uint8_t *)pwf+36,8))==-1) return false;
} else {
return false;
}
} else {
return false;
}
if( memcmp(pwf->ChunkID, "RIFF", 4)
||memcmp(pwf->Format, "WAVE", 4)
||memcmp(pwf->Subchunk1ID, "fmt ", 4)
||memcmp(pwf->Subchunk2ID, "data", 4))
return false;
else return true;
}
boolean play(WAV_FORMAT *pwf, char *filename) {
if(!(file = SD.open(filename))) return false;
if (getWavFormat(pwf)) {
I2S_Init(pwf->SampleRate,pwf->BitPerSample, pwf->NumChannels);
long len = pwf->Subchunk2Size;
while(len>0) {
if (len>I2S_BUFFER_SIZE) {
file.readBytes(i2s_buf, I2S_BUFFER_SIZE);
I2S_Write(i2s_buf, I2S_BUFFER_SIZE);
} else {
file.readBytes(i2s_buf, len);
I2S_Write(i2s_buf, len);
break;
}
len -= I2S_BUFFER_SIZE;
}
file.close();
}
return true;
}
void setup() {
WAV_FORMAT *pwf[1];
if (SD.begin( SD_CS, SPI, SDSPEED)) {
pwf[0] = (WAV_FORMAT *)malloc(sizeof(WAV_FORMAT));
play(pwf[0],"/sound/03_toccatina_low.wav");
}
}
void loop() {}
■ビルド環境
ソースコードのビルドには、PlatformIOを使用しています。
Arduino開発環境構築 PlatformIO

ビルドには、Raspberry Pi 3 model B、Raspberry Pi OS を使用しています。ラズベリーパイに Nano HAT Hacker を挟んで、GPIOを分岐させ、Piano Hat を被せています。
●電磁波の影響
ブレッドボード上に構築したので、シールドしていません。
ノートパソコンなどの近くで再生すると、電磁波の影響を受けてノイズが発生してしまいます。
■補足:PCM5102A I2C DAC
PCM5102Aの場合も、配線を変えるだけで、上記のソースコードがそのまま使えます。
PCM5102Aにはアンプが内蔵されていないので、外部アンプを取り付けてください。
モジュール供給電圧: dc3.3v
信号対ノイズ比snr: 112db
ダイナミックレンジ: 112dB
フルスケールシングルエンド出力: 2.1vrms
サンプリング周波数: 8KHz〜384KHz
メインクロック周波数: 50mhz
オーディオデータ数: 16、24、32
作業環境温度: -40 ° c〜85 ° c
オーディオデータ形式: iis,左揃え
全調波歪み (-1dbfsでのthd + n): -93db
●配線
| FlashSD/microSD | - | ESP32-S2 | - | PCM5102A |
| | - | A0 | - | BCK |
| | - | A2 | - | DIN |
| | - | A1 | - | LCK |
| GND | - | GND | - | GND |
| Vin | - | 3.3V | - | VIN |
| CS | - | A3 | | |
| (RasPi)RX | - | TX | | |
| (RasPi)TX | - | RX | | |
| SCK | - | SCK | | |
| MISO | - | MISO | | |
| MOSI | - | MOSI | | |
|
Raspberry Pi(ラズベリー パイ)は、ARMプロセッサを搭載したシングルボードコンピュータ。イギリスのラズベリーパイ財団によって開発されている。
たいていのことは100日あれば、うまくいく。長田英知著
「時間がなくて、なかなか自分のやりたいことができない」
「一念発起して何かを始めても、いつも三日坊主で終わってしまう」
「色んなことを先延ばしにしたまま、時間だけが過ぎていく」
そこで本書では、そんな著者が独自に開発した、
まったく新しい目標達成メソッド「100日デザイン」について、
その知識と技術を、余すところなくご紹介します。
まんがで納得ナポレオン・ヒル 思考は現実化する
OLとして雑務をこなす日々に飽き足らず、科学者だった父が残した薬品を商品化すべく、起業を決意した内山麻由(27)。彼女はセミナーで知り合った謎の女性からサポートを得ながら、彼女と二人三脚でナポレオン・ヒルの成功哲学を実践し、さまざまな問題を乗り越えていく。
ヒル博士の<ゴールデンルール>に従い、仕事に、恋に全力疾走する彼女の、成功への物語。
今日は人生最悪で最高の日 1秒で世界を変えるたったひとつの方法 ひすいこたろう著
偉人の伝記を読むと、最悪な日は、不幸な日ではなく、新しい自分が始まる日であることがわかります。最悪な出来事は、自分の人生が、想像を超えて面白くなる兆しなのです。偉人伝を読むことで、このときの不幸があったおかげで、未来にこういう幸せがくるのかと、人生を俯瞰する視線が立ち上がるのです。
ご飯は私を裏切らない heisoku著
辛い現実から目を背けて食べるご飯は、いつも美味しく幸せを届けてくれる。
29歳、中卒、恋人いない歴イコール年齢。バイト以外の職歴もなく、短期バイトを転々とする日々。ぐるぐると思索に耽るけど、ご飯を食べると幸せになれる。奇才の新鋭・heisokuが贈るリアル労働グルメ物語!
【最新版Gemini 3に対応!】できるGemini (できるシリーズ)
Geminiを「最強の知的生産パートナー」として使いこなすための、実践的なノウハウを凝縮した一冊です。
基本的な操作方法から、具体的なビジネスシーンでの活用、日々の業務を自動化するGoogle Workspaceとの連携、さらには自分だけのオリジナルAIを作成する方法まで余すところなく解説します。
Rustプログラミング完全ガイド 他言語との比較で違いが分かる!
Rustの各手法や考え方を幅広く解説!
500以上のサンプルを掲載。実行結果も確認。
全24章の包括的なチュートリアル。
ポチらせる文章術
販売サイト・ネット広告・メルマガ・ブログ・ホームページ・SNS…
全WEB媒体で効果バツグン!
カリスマコピーライターが教える「見てもらう」「買ってもらう」「共感してもらう」すべてに効くネット文章術
小型で便利な Type-C アダプター USB C オス - USB3.1 オスアダプター
Type-C端子のマイコンボードをこのアダプタを介して直接Raspberry Piに挿すことができます。ケーブルなしで便利なツールです。
Divoom Ditoo Pro ワイヤレススピーカー
15W高音質重低音/青軸キーボード/Bluetooth5.3/ピクセルアート 専用アプリ/USB接続/microSDカード
電源供給USBケーブル スリム 【5本セット】
USB電源ケーブル 5V DC電源供給ケーブル スリム 【5本セット】 電源供給 バッテリー 修理 自作 DIY 電子工作 (100cm)
|