子どものおもちゃに曲を入れたくて、ESP32と自作NS4168アンプ基板を作った記録
2026年10月5日
この記事の要点
- ESP32の内蔵フラッシュ(LittleFS)にMP3を3曲入れ、I2SアンプのNS4168で再生できました
- 音が出なかった原因は、ピンの取り違え(DATAとSDATA)でした。サンプリングレートも疑いましたが、測り直して無関係だと分かりました
- アンプ部分はJLCPCBで自作の基板にして、3.3V給電で動くことを確認しました
はじめに
子ども向けのおもちゃに曲を入れて鳴らしたくて、ESP32の内蔵フラッシュにMP3を入れ、I2Sアンプで再生する構成を作りました。
できあがった構成はシンプルですが、音が出るまでに何度も無音とノイズで止まりました。試した順に記録を残します。
| やったこと | 結果 |
|---|---|
| 曲の保存先を決める | 内蔵フラッシュ(LittleFS)にした |
| ブレッドボードで鳴らす | I2Sアンプモジュールで再生できた |
| アンプ基板を作る | JLCPCBで製造し、3.3V給電で再生できた |
完成した構成

内蔵フラッシュのMP3をESP32がデコードし、I2Sの3本でNS4168に送ります。
使ったもの
| 項目 | 内容 |
|---|---|
| マイコン | ESP32 DevKit(ESP32-WROOM-32、フラッシュ4MB) |
| アンプ | NS4168搭載のI2Sアンプモジュール(MEMSマイクLMD2718付き)。あとで自作の基板に置き換え |
| スピーカー | 8Ω |
| 開発環境 | Arduino IDE 2.3.4、ESP32 core 3.3.10 |
| ライブラリ | ESP8266Audio 2.4.1(名前はESP8266ですがESP32で動きます) |
| 音源 | 無料で配布されているフリー音源。MP3、48kbps、44.1kHz、モノラルに変換 |
| 保存先 | 内蔵フラッシュのLittleFS(約2.9MBを割り当て) |
配線(モジュールを使ったとき)
| ESP32 | モジュール | 役割 |
|---|---|---|
| GPIO26 | BCLK | ビットクロック |
| GPIO25 | LRCLK | 左右のクロック |
| GPIO22 | SDATA | 音声データ |
| 5V(VIN) | 5V | 電源 |
| GND | GND | グラウンド |

このあと、アンプ部分を自作の基板に置き換えました。同じスケッチのまま、3.3V給電で動いています。
曲の保存先をどう決めたか
3つの方法を検討しました。
| 方法 | 追加する部品 | 今回の結果 |
|---|---|---|
| 音声IC(WT588F02B-8S) | 音声IC | 在庫が見つからず、試していない |
| microSDカード | カードモジュール、SPIの信号線4本 | 再生できた |
| 内蔵フラッシュ(採用) | なし | 再生できた |
音声IC:在庫が見つからなかった
おもちゃの音源といえば、音声IC(WT588Fなど)が定番です。最初はWT588F02B-8Sを考えました。
JLCPCBで実装まで頼む前提で在庫を調べたところ、次の状態でした(2026年8月に調べた時点)。
- JLCPCBの部品ページ(C2917616)は存在する
- LCSCの商品ページは404で、在庫検索にも出てこない
ほかの音声ICも同じように在庫が見つからなかったので、在庫が豊富なESP32とI2Sアンプ(NS4168、C910588)の組み合わせに切り替えました。
注意
在庫は調べた時点のものです。発注前に必ず自分で確認してください。
microSDカード:再生できたが、部品が増える
ESP32にmicroSDカードモジュールをSPIでつなぎ、カードに入れたMP3を再生する方法も試しました。こちらも再生できました。
| 項目 | microSDカード | 内蔵フラッシュ |
|---|---|---|
| 追加する部品 | DFPlayer mini、microSDカード、抵抗 | なし |
| 追加する配線 | I2Cの信号線2本(Tx、Rx)と電源 | なし |
| 曲の入れ替え | 別途SDカードに追加 | PCから書き込む |
| 容量 | 大きい | 今回は約2.9MB |
内蔵フラッシュなら、追加の部品も配線も要りません。今回は内蔵フラッシュにしました。

参考サイト:https://curiouser.sakura.ne.jp/lutamesta/doku.php/gimmickkouza/electronic_basic/6/2_dfplayer_uart
内蔵フラッシュ:4MBに3曲入った

4MBのフラッシュを「スケッチ1MB + LittleFS約2.9MB」に分けました。48kbpsで2分前後の曲が3曲入りました。
| ファイル | サイズ |
|---|---|
| 00001.mp3 | 681,866バイト |
| 00002.mp3 | 763,525バイト |
| 00003.mp3 | 742,992バイト |
| 合計 | 2,188,383バイト(領域の71%) |
MP3のサイズの計算
サイズ(バイト)= ビットレート(bps)× 秒数 ÷ 8
48kbpsで120秒の曲なら、48,000 × 120 ÷ 8 = 720,000バイトです。サンプリングレートは関係しません。
今回入れた3曲は、無料で配布されているフリー音源です。自分で用意した曲や市販の曲ではありません。
曲をESP32に入れる手順
| 手順 | やること | 使うもの |
|---|---|---|
| 1 | フラッシュを、スケッチ用と曲用に分ける | partitions.csv、Arduino IDE |
| 2 | MP3を48kbps・44.1kHz・モノラルに変換する | ffmpeg |
| 3 | MP3をまとめて、LittleFSのイメージにする | Python(littlefs-python) |
| 4 | イメージをESP32に書き込む | esptool |
1. パーティションを分ける
スケッチと同じフォルダにpartitions.csvを置き、Arduino IDEの「ツール → Partition Scheme」でCustomを選びます。
ファイル partitions.csv
# Name, Type, SubType, Offset, Size, Flagsnvs, data, nvs, 0x9000, 0x5000,otadata, data, ota, 0xe000, 0x2000,app0, app, ota_0, 0x10000, 0x100000,spiffs, data, spiffs, 0x110000, 0x2F0000,
1行が1つの領域です。上から順に、次の意味になります。
| 名前 | 開始位置 | サイズ | 用途 |
|---|---|---|---|
| nvs | 0x9000 | 20KB | 設定の保存 |
| otadata | 0xe000 | 8KB | 起動するアプリの情報 |
| app0 | 0x10000 | 1MB | スケッチ |
| spiffs | 0x110000 | 約2.9MB | 曲(LittleFSとして使う) |
補足
名前はspiffsのままで、LittleFSとして使えます。開始位置の0x110000は、あとで曲を書き込むときに使います。
2. MP3を変換する
コマンド MP3の変換
ffmpeg -i input.mp3 -ac 1 -ar 44100 -b:a 48k -map_metadata -1 00001.mp3
input.mp3が元の曲、00001.mp3が変換後のファイルです。
| オプション | 意味 |
|---|---|
-ac 1 | モノラルにする |
-ar 44100 | サンプリングレートを44.1kHzにする(16kHzや22.05kHzでも再生できます) |
-b:a 48k | ビットレートを48kbpsにする |
-map_metadata -1 | タグ情報を消す |
3. LittleFSのイメージを作る
本来はArduino IDE 2.x用のプラグイン(arduino-littlefs-upload)で、dataフォルダごとアップロードできます。
私の環境(Arduino IDE 2.3.4)ではコマンドパレットに項目が出てこなかったので、Pythonのlittlefs-pythonでイメージを作りました。
フォルダは次のように置きます。
フォルダの構成
作業フォルダ/├─ make_littlefs.py ← 下のスクリプト├─ data/│ ├─ 00001.mp3│ ├─ 00002.mp3│ └─ 00003.mp3└─ littlefs.bin ← 実行するとできる
Python make_littlefs.py
from littlefs import LittleFSimport os, globSIZE, BLOCK, PROG = 0x2F0000, 4096, 256fs = LittleFS(block_size=BLOCK, block_count=SIZE // BLOCK, read_size=PROG, prog_size=PROG, disk_version=0x00020000)for p in sorted(glob.glob("data/*.mp3")): with fs.open("/" + os.path.basename(p), "wb") as f: f.write(open(p, "rb").read())open("littlefs.bin", "wb").write(bytes(fs.context.buffer))
| 設定 | 値 | 意味 |
|---|---|---|
SIZE | 0x2F0000 | 曲の領域のサイズ。partitions.csvと合わせる |
BLOCK | 4096 | フラッシュのブロックサイズ |
PROG | 256 | 読み書きの単位 |
disk_version | 0x00020000 | LittleFSのバージョン2.0 |
実行は次の2行です。1行目はライブラリのインストールで、最初の1回だけです。
コマンド イメージを作る
py -m pip install littlefs-pythonpy make_littlefs.py
この設定で作ったイメージは、ESP32 core 3.3.10のLittleFSでマウントできました。
4. esptoolで書き込む
コマンド 曲のイメージを書き込む
py -m esptool --chip esp32 --port COM5 --baud 921600 write-flash 0x110000 littlefs.bin
| 部分 | 意味 |
|---|---|
--port COM5 | ESP32をつないだポート。自分の環境の番号に変える |
--baud 921600 | 書き込みの速度 |
write-flash | 書き込みのコマンド。esptool v5でwrite_flashから名前が変わった |
0x110000 | 書き込む位置。partitions.csvの値と合わせる |
littlefs.bin | 手順3で作ったイメージ |
注意
シリアルモニタを開いたままだと、ポートを開けず失敗します。閉じてから実行してください。
補足
曲とスケッチは別々に書き込みます。Arduino IDEの「→」ボタンで書き込まれるのはスケッチ(0x10000)だけです。
スケッチ
スケッチは5つの部分に分かれています。①〜⑤を上から順につなげると、1つのスケッチになります。
| 部分 | やっていること |
|---|---|
| ① 設定 | ピン番号、曲のリスト、音量を決める |
| ② 再生の開始と停止 | 曲のファイルを開き、MP3デコーダにつなぐ |
| ③ ファイル一覧 | LittleFSに入っているファイルを表示する |
④ setup() | 起動時に1回だけ動く。I2Sを設定し、1曲目を鳴らす |
⑤ loop() | 繰り返し動く。再生を進め、操作を受け付ける |
① 設定
Arduino ① 設定
#include <Arduino.h>#include <LittleFS.h>#include <AudioFileSourceLittleFS.h>#include <AudioFileSourceBuffer.h>#include <AudioFileSourceID3.h>#include <esp_partition.h>#include <AudioGeneratorMP3.h>#include <AudioOutputI2S.h>constexpr int PIN_I2S_BCLK = 26;constexpr int PIN_I2S_LRC = 25;constexpr int PIN_I2S_DOUT = 22; // モジュールのSDATAへconstexpr int PIN_NEXT_SW = 0; // 曲送りボタン(使わないなら -1)AudioGeneratorMP3 *mp3 = nullptr;AudioFileSourceLittleFS *file = nullptr;AudioFileSourceBuffer *buff = nullptr;AudioFileSourceID3 *id3 = nullptr;AudioOutputI2S *out = nullptr;const char *playlist[] = { "/00001.mp3", "/00002.mp3", "/00003.mp3" };constexpr size_t PLAYLIST_LEN = sizeof(playlist) / sizeof(playlist[0]);size_t currentTrack = 0;float gain = 0.3f;
自分の環境に合わせて変えるのは、次の4か所です。
| 変える場所 | 内容 |
|---|---|
PIN_I2S_BCLK / PIN_I2S_LRC / PIN_I2S_DOUT | I2Sの3本をつなぐGPIO |
PIN_NEXT_SW | 曲送りボタンのGPIO。使わないなら-1 |
playlist | 再生するファイル名。LittleFSに入れた名前と合わせる |
gain | 音量。0.0〜2.0(最初は0.3) |
② 再生の開始と停止
Arduino ② 再生の開始と停止
void stopPlayback() { if (mp3 && mp3->isRunning()) mp3->stop(); delete id3; id3 = nullptr; delete buff; buff = nullptr; delete file; file = nullptr;}bool startTrack(size_t index) { stopPlayback(); if (!LittleFS.exists(playlist[index])) { Serial.printf("ファイルがありません: %s\n", playlist[index]); return false; } file = new AudioFileSourceLittleFS(playlist[index]); buff = new AudioFileSourceBuffer(file, 8192); id3 = new AudioFileSourceID3(buff); if (!mp3) mp3 = new AudioGeneratorMP3(); out->SetGain(gain); bool ok = mp3->begin(id3, out); Serial.printf("再生: %s (%s)\n", playlist[index], ok ? "OK" : "失敗"); return ok;}
音声データは、次の順に流れます。
ファイル → バッファ → タグの処理 → MP3デコーダ → I2S出力
| クラス | 役割 |
|---|---|
AudioFileSourceLittleFS | LittleFSのファイルを読む |
AudioFileSourceBuffer | 読んだデータを8192バイトためる |
AudioFileSourceID3 | MP3の先頭にあるタグ情報を処理する |
AudioGeneratorMP3 | MP3をデコードする |
AudioOutputI2S | I2Sで出力する |
③ ファイル一覧
Arduino ③ ファイル一覧
void listFiles() { Serial.println("--- LittleFS 内のファイル ---"); File root = LittleFS.open("/"); for (File f = root.openNextFile(); f; f = root.openNextFile()) { Serial.printf(" %-16s %8u bytes\n", f.name(), (unsigned)f.size()); } Serial.printf("使用量: %u / %u bytes\n", (unsigned)LittleFS.usedBytes(), (unsigned)LittleFS.totalBytes());}
起動時に、曲が入っているかを確認するための表示です。
④ setup()
Arduino ④ setup()
void setup() { Serial.begin(115200); delay(300); if (PIN_NEXT_SW >= 0) pinMode(PIN_NEXT_SW, INPUT_PULLUP); // 曲の領域が想定どおりかを表示する const esp_partition_t *p = esp_partition_find_first( ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL); if (p) { Serial.printf("曲の領域: 開始=0x%06X サイズ=%u bytes\n", (unsigned)p->address, (unsigned)p->size); } else { Serial.println("曲の領域が見つかりません。Partition Scheme を Custom に"); } out = new AudioOutputI2S(); out->SetBuffers(12, 1024); out->SetPinout(PIN_I2S_BCLK, PIN_I2S_LRC, PIN_I2S_DOUT); out->SetRate(44100); out->SetGain(gain); if (!LittleFS.begin(false)) { Serial.println("LittleFS のマウントに失敗"); return; } listFiles(); startTrack(currentTrack); Serial.println("n=次の曲 / p=前の曲 / s=停止 / +,-=音量");}
| 順 | 処理 |
|---|---|
| 1 | シリアルを115200bpsで開始する |
| 2 | 曲送りボタンのピンを入力にする |
| 3 | 曲の領域の位置とサイズを表示する |
| 4 | I2S出力を作り、ピンと44.1kHzを設定する |
| 5 | LittleFSをマウントし、ファイル一覧を表示する |
| 6 | 1曲目を再生する |
⑤ loop()
Arduino ⑤ loop()
void loop() { if (mp3 && mp3->isRunning()) { if (!mp3->loop()) { mp3->stop(); currentTrack = (currentTrack + 1) % PLAYLIST_LEN; startTrack(currentTrack); } } if (Serial.available()) { char c = Serial.read(); if (c == 'n') { currentTrack = (currentTrack + 1) % PLAYLIST_LEN; startTrack(currentTrack); } else if (c == 'p') { currentTrack = (currentTrack + PLAYLIST_LEN - 1) % PLAYLIST_LEN; startTrack(currentTrack); } else if (c == 's') { stopPlayback(); } else if (c == '+' || c == '-') { gain += (c == '+') ? 0.1f : -0.1f; gain = constrain(gain, 0.0f, 2.0f); if (out) out->SetGain(gain); Serial.printf("gain=%.1f\n", gain); } } if (PIN_NEXT_SW >= 0 && digitalRead(PIN_NEXT_SW) == LOW) { delay(30); if (digitalRead(PIN_NEXT_SW) == LOW) { currentTrack = (currentTrack + 1) % PLAYLIST_LEN; startTrack(currentTrack); while (digitalRead(PIN_NEXT_SW) == LOW) delay(10); } }}
| 処理 | 内容 |
|---|---|
| 再生を進める | mp3->loop()を呼び続ける。曲が終わったら次の曲へ進む |
| シリアルの操作 | 文字を受け取って、曲送りや音量の変更をする |
| ボタンの操作 | GPIO0のボタンが押されたら、次の曲へ進む |
操作は次のとおりです。
| 操作 | 動作 |
|---|---|
シリアルでn | 次の曲 |
シリアルでp | 前の曲 |
シリアルでs | 停止 |
シリアルで+ / - | 音量を上げる / 下げる |
| GPIO0のボタン | 次の曲 |
起動時の表示
次のように表示されれば、曲は正しく入っています。
表示 シリアルモニタ
曲の領域: 開始=0x110000 サイズ=3080192 bytes--- LittleFS 内のファイル --- 00001.mp3 681866 bytes 00002.mp3 763525 bytes 00003.mp3 742992 bytes再生: /00001.mp3 (OK)
| 表示 | 意味 |
|---|---|
曲の領域: 開始=0x110000 | パーティションがpartitions.csvのとおりになっている |
00001.mp3〜00003.mp3 | 3曲が入っている |
再生: /00001.mp3 (OK) | 1曲目の再生を始めた |
音が出るまでにハマったところ
試した構成の移り変わり
| 順 | ESP32の出力 | アンプ | 結果 |
|---|---|---|---|
| 1 | 内蔵DAC(GPIO25/26) | PAM8403(GF1002) | ザーというノイズだけ |
| 2 | NoDAC(1ビット出力 + RCフィルタ) | PAM8403(GF1002) | 鳴るが、サーというノイズが乗る |
| 3 | I2S | NS4168のモジュール | 最初は無音。配線を直して鳴った |
| 4 | I2S | NS4168の自作基板 | 鳴った |
ハマったところの一覧
| 番号 | 症状 | 原因 | 対処 |
|---|---|---|---|
| 1 | 内蔵DACから、ザーというノイズしか出ない | ライブラリの古い書き方が非推奨になっていた | 別の出力方法に進んだ |
| 2 | 曲は鳴るが、サーというノイズが乗る | NoDAC出力では下げきれなかった | I2S入力のアンプに切り替えた |
| 3 | 無音 | マイク用のDATAにつないでいた | SDATAにつなぎ直した |
| 4 | 16kHzの音源が無音 | 3番の配線ミスの状態で試していた(レートは無関係) | 配線を直したあとに測り直した |
| 5 | LittleFSをマウントできない | 不明 | イメージを書き直した |
1. 内蔵DACから音が出ない
最初はI2Sアンプを持っていなかったので、ESP32の内蔵DAC(GPIO25/26)からアナログアンプ(PAM8403搭載のGF1002)に入れようとしました。
Arduino 音が出なかった書き方
out = new AudioOutputI2S(0, AudioOutputI2S::INTERNAL_DAC);
コンパイルは通りますが、スピーカーからはザーというノイズしか出ませんでした。
ESP8266Audio 2.4.1では、この書き方は非推奨です。AudioOutputI2S.hのコンストラクタに、DAC出力にはAudioOutputInternalDAC()を使うようにというメッセージが付いています。
補足
私はAudioOutputInternalDACを試していません。このあと別の方法に進んだためです。
切り分けには、ライブラリを使わずdacWrite()だけでサイン波を出すスケッチが役立ちました。これで音が出たので、配線とアンプは正常で、原因はライブラリの使い方だと分かりました。
2. NoDAC出力 + PAM8403は、鳴るがノイズが乗る
次にAudioOutputI2SNoDACを試しました。I2Sのデータピンに1ビットの信号を出し、RCフィルタ(1kΩ + 10nF)で平滑して、アナログアンプに入れる方法です。
曲は鳴りましたが、サーというノイズが常に乗りました。オーバーサンプリングを上げたり、コンデンサを22nFに替えたりしても、気にならない程度までは下がりませんでした。
ここで、I2S入力のアンプに切り替えることにしました。
3. モジュールの「DATA」は、アンプの入力ではなかった
買ったのは、NS4168(アンプ)とLMD2718(MEMSマイク)が載ったモジュールです。データ線が2本あります。
| ピン | 役割 |
|---|---|
| CLK | マイクのクロック |
| DATA | マイクのデータ(ここにつないでいた) |
| LRCLK | アンプのLRクロック |
| BCLK | ビットクロック |
| SDATA | アンプのI2Sデータ入力(正しい接続先) |
DATAに音声を流していたので無音でした。商品ページのピン説明を読んでSDATAにつなぎ直したら鳴りました。
シリアルモニタには「再生: OK」と出続けるので、ソフト側の問題だと思い込み、ここで一番時間を使いました。
4. 16kHzの音源が無音だった(原因は配線ミスだった)
容量を節約するために、最初はMP3を32kbps・16kHzで作っていました。これが無音でした。
同じメロディを出力レートだけ変えて鳴らすと、44100Hzのときだけ音が出ました。そこでサンプリングレートが原因だと考え、MP3を44.1kHzで作り直しました。
訂正(2026年10月)
公開時は「16kHzの音源だと無音になる」と書いていました。これは誤りでした。
当時のテストは、データ線をマイク側のDATAにつないだ状態(3番の配線ミス)で行ったものです。SDATAにつなぎ直したあと、16kHzを試し直していませんでした。
Xで「アンプのピンでLRCLKとBCLKを測るとよい」と助言をもらい、配線を直した状態で測り直しました。
クロックの測定
ESP32のパルスカウンタで、アンプ基板のピンに出ているLRCLKとBCLKを数えました。音源は440Hzの連続音で、レートだけを変えています。
| 設定したレート | LRCLK | BCLK | BCLK ÷ LRCLK | 音 |
|---|---|---|---|---|
| 44100Hz | 44,106Hz | 1,411,318Hz | 32.0 | 鳴る |
| 16000Hz | 15,999Hz | 512,018Hz | 32.0 | 鳴る |
| 22050Hz | 22,058Hz | 705,659Hz | 32.0 | 鳴る |
| 32000Hz | 31,998Hz | 1,024,095Hz | 32.0 | 鳴る |
| 48000Hz | 48,006Hz | 1,536,193Hz | 32.0 | 鳴る |
表は、44100Hzで開始してからSetRate()で切り替えたときの値です。最初からそのレートで開始した場合も、同じ結果でした。
MP3での確認
続けて、同じ曲を3通りのMP3にして再生しました。当時無音だった16kHzのファイルも鳴りました。
| ファイル | 結果 |
|---|---|
| 44.1kHz・48kbps | 鳴る |
| 16kHz・32kbps(当時無音だったファイル) | 鳴る |
| 22.05kHz・32kbps | 鳴る |
分かったことは次のとおりです。
- 16kHzの音源は再生できる。サンプリングレートは無音の原因ではなかった
- 配線を間違えていたときに、44100Hzだけ音が出た理由は分かっていない
- 配線を直したら、それ以前の切り分けの結果は取り直す必要がある
補足
ESP8266AudioのMP3デコーダは、再生するファイルに合わせて出力レートを設定し直します。setup()のSetRate(44100)があっても、16kHzのファイルは16kHzのまま出力されます。
確認は自作基板(3.3V給電)で行いました。同じ曲で比べると、16kHz・32kbpsのファイルは454,833バイトで、44.1kHz・48kbps(681,866バイト)の約3分の2です。高い音は削られますが、容量を優先するなら選べます。
5. Corrupted dir pair でマウントに失敗する
スケッチを何度も書き換えているうちに、LittleFSがマウントできなくなりました。
表示 シリアルモニタ(エラー)
E (326) esp_littlefs: ... lfs.c:1383:error: Corrupted dir pair at {0x1, 0x0}E (327) esp_littlefs: mount failed, (-84)
LittleFSのイメージを同じアドレスに書き直したら直りました。壊れた理由は分かっていません。
6. 細かいもの
| エラー | 原因 |
|---|---|
setup()とloop()が重複する | スケッチのフォルダに.inoを2つ置いた |
main file missing | フォルダ名と.inoの名前が違う |
Partition SchemeのCustomが効かない | 開いているスケッチのフォルダにpartitions.csvがない |
NS4168でアンプ基板を作る
モジュールで動いたので、マイクを外したアンプだけの基板を設計し、JLCPCBで製造しました。
| 項目 | 内容 |
|---|---|
| アンプIC | NS4168(ESOP-8) |
| 層数 | 2層 |
| サイズ | 25mm × 18mm |
| 電源 | 3.3V |
| 入力 | J1(6ピン) |
| 出力 | J2(2ピン、スピーカー用) |
| 製造 | JLCPCB(部品実装まで) |

回路の要点
| 部品 | 値 | 役割 |
|---|---|---|
| U1 | NS4168(ESOP-8) | I2S入力のD級アンプ |
| C1 / C2 / C3 | 100µF / 1µF / 100nF | 電源のパスコン |
| R1 | 10kΩ | CTRLをVDDへ(右チャンネル) |
| R3〜R5 | 33Ω | I2Sの3本に直列 |
| R6 | 1kΩ | シャットダウン制御用 |
| FB1 / FB2 | フェライトビーズ | 出力のEMI対策 |
| C4 / C5 | 1nF | 出力のEMI対策 |
パスコン(100µF + 1µF)と、出力のビーズ(1000Ω)+ 1nFは、データシート(NS4168 データシート Apr.2023 V1.2、Nsiway)の記載どおりです。
部品表
JLCPCBで実装してもらう前提で、LCSCの部品番号を付けています。
| 部品 | 値 | LCSC | 区分 |
|---|---|---|---|
| U1 | NS4168 | C910588 | Extended |
| C1 | 100µF 1206 | C15008 | Basic |
| C2 | 1µF 0603 | C15849 | Basic |
| C3 | 100nF 0603 | C14663 | Basic |
| C4, C5 | 1nF 0603 | C1588 | Basic |
| R1 | 10kΩ 0603 | C25804 | Basic |
| R3〜R5 | 33Ω 0603 | C23140 | Basic |
| R6 | 1kΩ 0603 | C21190 | Basic |
| FB1, FB2 | BLM21AG102SN1D 0805 | C85835 | Extended |
電源を3.3Vにした理由
NS4168のデジタル入力のしきい値は、電源電圧に比例します(VIH = 0.7 × VDD)。ESP32の出力は3.3Vです。
| 電源(VDD) | 必要なVIH | ESP32の出力 | 判定 |
|---|---|---|---|
| 3.3V | 2.31V | 3.3V | 規格内 |
| 5.0V | 3.50V | 3.3V | 規格外 |
モジュールは5Vで動いていましたが、基板にするなら規格内に収まる3.3Vにしました。レベルシフタも要りません。
出力は下がります(データシート値:3.6V・4Ωで1.2W、5V・4Ωで2.5W)。
注意
データシートには、電源電圧の記載が2通りあります。特性一覧は3.0〜5.5V、絶対最大定格は2.8〜5.0Vです。
絶対最大定格のほうが狭いのはおかしいので、5V以下で使うのが無難だと判断しました。
CTRLピン
CTRLは、電圧で3つの状態を切り替えます。
| CTRL電圧 | 動作 |
|---|---|
| 1.5V〜VDD | 右チャンネル |
| 0.9〜1.15V | 左チャンネル |
| 0〜0.4V | シャットダウン(1µA) |
ESP8266Audioはモノラル音源を左右両方に出すので、どちらのチャンネルでも同じ音になります。R1でVDDにつないで右チャンネルにしました。
I2SはI2Cではない
名前が似ていますが別物です。
| 項目 | I2S | I2C |
|---|---|---|
| 用途 | 音声データ | センサーなどの制御 |
| 信号線 | BCLK・LRCLK・SDATAの3本 | SCL・SDAの2本 |
| プルアップ抵抗 | 不要 | 必要 |
発注前の見直しで見つかったミス
発注前に、回路図・BOM・ガーバーをそれぞれ見直しました。8件見つかりました。
| 対象 | 見つかったミス | 対処 |
|---|---|---|
| 回路図 | BCLKとLRCLKが、U1で入れ違っていた | 入れ替えた |
| 回路図 | C3が100µFになっていた | 100nFに直した |
| BOM | 100nFに選んだ部品(C85864)の在庫が23個しかなかった | C14663に替えた |
| BOM | ビーズの番号(C16902)が1206サイズで、0805のフットプリントに載らない | C85835に替えた |
| BOM | アップロードするCSVの、LCSC番号の列が空だった | 番号を入れて作り直した |
| BOM | 抵抗とコンデンサが、すべてExtended部品だった | Basic部品に替えた |
| レイアウト | 出力の配線が0.15mmと細かった | 太くしてから発注した |
| レイアウト | スピーカーのコネクタが出力から遠い | 直さずに発注した |
回路図
NS4168は、Pin2がLRCLK、Pin3がBCLKです。ネット名を上から順に並べたら逆になっていました。
BOM
Basic部品に替えると、Extended部品にかかる追加の手数料を減らせます。直したあとの部品表が、上の「部品表」です。
レイアウト
ガーバーの銅箔のつながりを回路図と突き合わせて、ショートや未接続がないことを確認しました。
データシートは、出力の配線を「短く太く」するよう求めています。コネクタの位置は今回そのままで、SPK+が裏面のGNDベタの中を長く走っています。次に作り直すときは、コネクタを出力の近くに移すつもりです。

届いた基板での動作確認
届いた基板をESP32 DevKitにつないで確認しました。スケッチはブレッドボードのときと同じものです。

| 基板のJ1 | 信号 | ESP32 |
|---|---|---|
| 1 | 3V3 | 3V3 |
| 2 | LRCLK | GPIO25 |
| 3 | BCLK | GPIO26 |
| 4 | SDATA | GPIO22 |
| 5 | SD | GPIO27(シャットダウンを試すときだけ接続) |
| 6 | GND | GND |
| 確認したこと | 結果 |
|---|---|
| 3.3V給電での再生 | 鳴った(44.1kHzのMP3、ESP32の3V3から給電) |
| ノイズ | 気にならなかった(NoDAC出力のときのサーという音と比べて) |
| 音量 | 十分だった(モジュールは5Vだったが、3.3Vでも足りた) |
| SDピンでのシャットダウン | 音が止まった |
| シャットダウン中の消費電流 | 未測定 |

SDピンでシャットダウンできるか

SDピンは、R6(1kΩ)を通してCTRLにつながっています。CTRLはR1(10kΩ)で3.3Vに引き上げてあります。
| GPIO27の状態 | CTRLの電圧 | アンプ |
|---|---|---|
| 入力(Hi-Z) | 3.3V | 動く |
| Low出力 | 約0.3V(計算値) | シャットダウン |
テスト用に、440Hzの連続音を出したまま、SDピンだけを切り替えるスケッチを書きました。切り替える部分は次のとおりです。
Arduino SDピンを切り替える部分
constexpr int PIN_AMP_SD = 27; // 基板の SD へvoid setAmp(bool on) { if (on) { pinMode(PIN_AMP_SD, INPUT); // Hi-Z。R1のプルアップでCTRL = 3.3V } else { digitalWrite(PIN_AMP_SD, LOW); // 先にLowを用意してから出力にする pinMode(PIN_AMP_SD, OUTPUT); // CTRL = 約0.3V }}
SDピンをLowにすると、音が止まりました。ESP32はI2Sの信号を出し続けているので、止まったのはアンプ側です。
補足
CTRLの電圧(約0.3V)は計算値で、実測はしていません。シャットダウンの上限は0.4Vなので、余裕は約0.1Vです。
まだ確認していないこと
| 項目 | 状況 |
|---|---|
| モジュール(5V)での16kHz再生 | 未確認。自作基板では再生できた |
AudioOutputInternalDACを使った内蔵DAC出力 | 未検証 |
| シャットダウン中の消費電流、CTRL電圧の実測 | 未測定 |
| スピーカーのコネクタを出力の近くに移した基板 | 次に作り直すときに対応 |
まとめ
- ESP32の内蔵フラッシュ(LittleFS)にMP3を入れ、ESP8266AudioとI2Sアンプで再生できた
- 無音の原因は、ピンの取り違え(DATAとSDATA)だった。サンプリングレートを疑ったのは、配線ミスの状態で試した結果だった
- シリアルに「再生OK」と出ていても、音が出ているとは限らない。ライブラリを使わない最小のテスト(サイン波、内蔵メロディ)で、配線とソフトを分けて確認すると早い
- 基板のデータは、発注前に回路図・BOM・ガーバーをそれぞれ見直すとミスが見つかる。今回は見直しで直したおかげで、届いた基板は1回目で動いた
- NS4168は3.3V給電でも、おもちゃ用の小さなスピーカーには十分な音量が出た
