HOME | Raspberry Pi | ビジネス書籍紹介 | 2026-01-04 (Sun) Today's Access : 295 Total : 1260993. Since 10 Sep. 2019

シンプルな16bit DAC
2024.08.08/2025.11.23更新

以前、Adafruit QT Py ESP32-S2 の内蔵DACを用いて、8ビット・モノラル WAVファイルの再生を行いました。

内蔵DACによるWAV再生

今回は、16bit DAC TM8211 を使って、16ビット・ステレオ WAV音源を鳴らせてみます。

●Features and Specification of TM8211

TM8211はPT8211の後継互換品です。デジタルオーディオ用に設計されたR-2Rラダー抵抗型CMOS16bitD/Aコンバータです。 低THD、両チャンネル位相差0(typ)、またラダーネットワークの採用で高速変換が可能です。
・3-6V Operating voltage, suitable for 3.3V & 5.0V operations.
・This D/A converter uses CMOS Technology
・It supports 3.3 V bus input level
・Low power consumption
・16-bit dynamic range
・Low total harmonic distortion
・In this microcontroller Two audio channel output in the same chip
・No phase shift between both output channel
PT8211 16-Bit DAC

TM8211を使うメリット
・符号付16ビットステレオWAV音源に対応している
・専用ライブラリを必要とせずArduino基本関数のみで構築可能
・WAV音源編集に対して自由度が高い
・周辺の電磁波ノイズの影響を受けにくい

補足:R-2Rラダー回路
R-2Rラダー回路とは、抵抗値Rと2Rの2種類の抵抗をラダー状に配置した、デジタル信号をアナログ信号に変換するD/A変換回路の一種です。 デジタル信号の各ビットが、抵抗ネットワークの特定のスイッチ(アナログスイッチ)をON/OFFすることで、それぞれのビットの重みに応じた電圧レベルを生成し、アナログ信号として出力します。

補足:ΔΣ(デルタシグマ)変調
パルス変調技術の一種で、アナログ信号をデジタル信号に変換する際に用いられます。 この技術は、Δ(デルタ)が引き算(差分)、Σ(シグマ)が足し算(積分)を表しており、信号の「差」を積分して処理することで、ノイズを抑えながら高分解能な変換を実現します。 主に、オーディオ機器に用いられる「デルタシグマ型ADコンバーター(アナログ・デジタル変換器)」などで使われています。 入力信号を、非常に速いクロックで1bitのデジタル信号のパルス列に変換します。

PT8211 Pinout



PT8211 Interfacing_Diagram


データシートの参考回路通りに組んでもよいのですが、直接スピーカー・アンプに繋いで問題ありません。 電源ノイズ対策用のキャパシタも必要ありません。

●LM385N
参考回路通りに組む場合に使える単電源オペアンプです。
バイポーラー/ 回路数:2/ 動作電源電圧min.:3V/ 動作電源電圧max.:32V/ 入力オフセット電圧:2mV/ 入力バイアス電流:20nA/ 入力オフセット電流:2nA/ 消費電流:0.7mA



●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 ESP32-S2 Pinout


●配線
 ESP32-S2     TM8211     Speaker Amp. 
A0  -  1:BCK       
A1  -  2:WS       
A2  -  3:DIN       
GND  -  4:GND  -  GND 
3V  -  5:VCC       
    6:LCH  -  LEFT 
    8:RCH  -  RIGHT 
    microSD       
A3  -  CS       
SCK  -  SCK       
MISO  -  MISO       
MOSI  -  MOSI       
3V  -  Vin       
GND  -  GND       


回路をユニバーサル基板上に纏めています。


左のラズベリーパイはビルドおよび電源として利用しています。 中心上部、MP3ラジオの外部入力端子に繋いでアンプ内蔵スピーカ―として利用しています。

●音源の準備
AudaCityに音源を取り込んで、WAV形式で書き出します。

44.1KHz ステレオWAV音源では、SDカードモジュールからの読み込みに時間が掛かってしまい、演奏が遅延してしまいます。 サンプリング・レートを11.025KHz、符号付き16bit-PCMに落として出力します。 モノラルにして、22.05KHz、符号付き16bit-PCMとしてもファイルサイズは変わりません。

●コード解説

掲載用にコードは簡素化しています。
tm8211.ino
#include <SPI.h>
#include <SD.h>
#include <FS.h>

#define SD_CS   A3
#define SDSPEED 40000000

#define PIN_BCK    A0   // 1:BCK
#define PIN_WS     A1   // 2:WS
#define PIN_DIN    A2   // 3:DIN

#define NOP __asm__ __volatile__ ("nop\n\t")

#define BUFFER_SIZE  2048

#define NORMAL      1.0
#define LIGHT_LOLI  0.6
#define HEAVY_LOLI  0.5

#define ETS_DELAY_US_RATE  NORMAL
//#define ETS_DELAY_US_RATE  LIGHT_LOLI
//#define ETS_DELAY_US_RATE  HEAVY_LOLI

// 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;
} WAV_FORMAT;


File       file;
WAV_FORMAT pwf;
uint8_t    buf[BUFFER_SIZE];

boolean getWavFormat()
{
	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;
}

void writeDACChannel(int16_t mono)
{
	unsigned char pos = 16;

	// Send data into PT8211 in least significant bit justified (LSBJ) format.
	while(pos > 0) {
		pos--;
		digitalWrite(PIN_BCK, LOW);
		digitalWrite(PIN_DIN, (mono & (1 << pos)) ? HIGH: LOW);
		NOP;
		// Toggle BCK.
		digitalWrite(PIN_BCK, HIGH);
		NOP;
	}
}

boolean writeDAC(char *filename)
{
	unsigned short left, right, delayus;
	long block;

	if(!(file = SD.open(filename))) return false;

	getWavFormat();
	delayus = (1000000 / pwf.SampleRate) * ETS_DELAY_US_RATE;
	long len = pwf.Subchunk2Size;

	while(len>0) {
		if (len>BUFFER_SIZE) block = BUFFER_SIZE; else block = len;
		file.readBytes((char*)buf, block);
		for(int pos=0;pos<block;pos+=4)
		{
			left  = buf[pos+1];
			left  = (left<<8) + buf[pos];
			right = buf[pos+3];
			right = (right<<8) + buf[pos+2];

			digitalWrite(PIN_WS,  LOW); // right channel
			writeDACChannel(left);

			digitalWrite(PIN_WS,  HIGH); // left channel
			writeDACChannel(right);

			ets_delay_us(delayus);
		}
		len -= BUFFER_SIZE;
	}
	file.close();

	return true;
}

void setup()
{
	pinMode(PIN_BCK, OUTPUT);
	pinMode(PIN_WS,  OUTPUT);
	pinMode(PIN_DIN, OUTPUT);

	if (SD.begin( SD_CS, SPI, SDSPEED)) {
		writeDAC("/sound/odeon_16bit11KHz.wav");
	}
}

void loop() {}

getWavFormat();
WAVファイルのヘッダー情報を取得しています。

delayus = (1000000 / pwf.SampleRate) * ETS_DELAY_US_RATE;
取得したサンプリング周波数に応じて、TM8211へのデータ送信間隔を算出しています。

while(len>0) {
 if (len>BUFFER_SIZE) block = BUFFER_SIZE; else block = len;
 file.readBytes((char*)buf, block);
ここではある程度纏まった単位でデータを読み込んでいます。 ステレオ16ビットWAV音源では1データの最小単位は左右合わせて4バイトですが、 この単位で頻繁に読み込みを行うと処理への負荷が高まり、音の再生が安定しません。

for(int pos=0;pos<block;pos+=4) {
 left = buf[pos+1];
 left = (left<<8) + buf[pos];
 right = buf[pos+3];
 right = (right<<8) + buf[pos+2];
16ビット・ステレオデータは符号付き2バイト整数値で左→右→左→右のように格納されています。 さらにこの2バイトデータは下位バイト・上位バイトの並びで格納されているので、並び替えて変数に格納します。

digitalWrite(PIN_WS, LOW); // right channel
writeDACChannel(left);

digitalWrite(PIN_WS, HIGH); // left channel
writeDACChannel(right);
ここではTM8211を制御しています。 送信タイミングは下記のようになります。

WSピンをLOWあるいはHIGHにすることで、ステレオ音源の左右データのどちらを送信するかを指定します。
BCKピンをLOWにして、音源データ1ビットを送信後にHIGHに戻し、これを繰り返します。

for (int pos = 15; pos >= 0; pos--) {
 digitalWrite(PIN_BCK, LOW);
 digitalWrite(PIN_DIN, (mono & (1 << pos)) ? HIGH: LOW);
 NOP;
 // Toggle BCK.
 digitalWrite(PIN_BCK, HIGH);
 NOP;
}
音源データを上位ビットから送信しています。 また、送信直後にNOP(No Operation)命令を入れています。

NOP(No OPeration)に関して、インラインアセンブラで定義しています。
#define NOP __asm__ __volatile__ ("nop\n\t")
CPUが特定のデータを読み込んでから、次の処理を開始するためには、少しの間データを保持する必要があります。 その間、NOP命令を実行することで正確な処理が実行されるようになり、CPUの動作が安定します。
ノーオペレーション命令 NOPとは?

ets_delay_us(delayus);
左右1セットのデータを送信したあとで、次のデータ送信間隔を調整することで、周波数を圧縮、伸長することができます。
音声データであれば、データ送信間隔を短くすると高い声になります。

●開発環境
ソースコードのビルドには、PlatformIOを使用しています。
Arduino開発環境構築 PlatformIO

●WAV音源の再生
実際に再生した様子はYouTube動画をみてみてください。
暮れてゆく空は
アーティスト: 遊佐未森
アルバム: ハルモニオデオン
リリース: 1989年


●余談
ソースコードの下記の部分を変更してビルド、再生してみてください。 ちょっとだけ楽しめます。

#define ETS_DELAY_US_RATE  NORMAL
//#define ETS_DELAY_US_RATE  LIGHT_LOLI
//#define ETS_DELAY_US_RATE  HEAVY_LOLI
  ↓
//#define ETS_DELAY_US_RATE  NORMAL
//#define ETS_DELAY_US_RATE  LIGHT_LOLI
#define ETS_DELAY_US_RATE  HEAVY_LOLI

ここでは波形圧縮のみですが、TM8211を使うと独自に波形を作り込んでリアルタイムに再生するということも簡単にできてしまいます。

●参考情報
dilshan/pt8211-dac.ino
Raspberry Pi(ラズベリー パイ)は、ARMプロセッサを搭載したシングルボードコンピュータ。イギリスのラズベリーパイ財団によって開発されている。
2020.01.05 第1回 abcjs 楽譜作成・演奏スクリプト
2020.01.09 I2S通信によるハイレゾ音源再生
2020.01.18 MIDI再生:FM音源YMF825+Arduino編
2020.01.24 FM音源YMF825+micro:bit編
2020.02.13 Piano Hat & Rosegarden
2020.03.18 テキスト読み上げ gTTS
2020.05.19 テキスト読み上げ AquesTalk pico LSI
2020.06.22 波形処理 第1回 音の波と三角関数
2020.07.22 波形処理 第2回 平均律と純正律
2020.08.26 波形処理 第3回 黒鍵と白鍵
2020.11.21 深層学習 第1回環境整備
2020.12.19 深層学習 第2回マルコフ連鎖・自動歌詞生成
2021.01.02 深層学習 第3回コード進行解析
2021.01.16 波形処理 第4回 コード演奏
2021.08.07 MIDI制御/Adafruit Music Maker
2021.08.23 MIDIフォーマット解析
2021.10.10 音声ファイルの切貼り
2022.09.16 USB-MIDI
2023.01.16 MAX98537 & PCM5102
2023.03.15 音源サンプリング
2023.06.16 ヤマハ音源IC YMZ294
2024.01.07 内蔵DACによるWAV再生
2024.03.23 Piano Hat for MIDI
2024.08.08 シンプルな16bit DAC
2024.09.09 ESP32-S3 USB MIDI
2024.11.10 音声変換・参照音声編集
2024.11.24 音声変換 Seed-VC
2024.12.11 音源IC SN76489
2025.01.10 ttymidi + SAM2695
2025.02.08 YMF825 + ESP32
2025.05.08 Small World 4MH711
YAMAHA YMU251-D
2025.05.23 Small World 4MH711
NJM2073
2025.06.08 Small World 4MH711
オリジナル・クロック
2025.06.23 Bluetooth Emitter
2025.11.20 NANO ESP32 統合環境
2025.11.24 MIDIキーボード→VS1053再生
2025.12.08 MP3 Player Shield

たいていのことは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)

Copyright © 2011-2027 Sarako Tsukiyono All rights reserved®.