メインコンテンツまでスキップ

XIAO ESP32-S3 Plus による reSpeaker XVF3800 44mm の音量制御

このスケッチでは、XIAO ESP32-S3 Plus が I2S 経由でオンボードの AIC3104 コーデックから生成した 440 Hz トーンを再生し、シリアルモニタに + または - を入力して音量を調整します。m を入力すると出力先がヘッドホン(HPLOUT/HPROUT)とラインアウト(LOP)の間で切り替わり、コーデックの設定はすべて I2C 経由で行われます。トーンはコード内で合成されるため、オーディオファイルは不要です。

前提条件​

  • XVF3800 が I2S クロックを供給するため、アレイは I2S ファームウェア を実行している必要があります。詳しくは Update Firmware を参照してください。
  • ESP32 ボードパッケージと Arduino Audio Tools ライブラリをインストールした Arduino IDE。詳しくは Software Preparation を参照してください。
  • ボードに XIAO_ESP32S3_Plus を選択し、ポート番号を選択していること。
  • JST コネクタに接続したスピーカー、または 3.5 mm ジャックに接続したヘッドホン。
注記

AIC3104 は XVF3800 と I2C バスを共有しています。XMOS ファームウェアとオンボードの音量ボタンも同じレジスタに書き込むため、XIAO から設定したレベルが後で上書きされる場合があります。

音量マッピング​

このスケッチでは、0〜17 の音量スケールを公開しており、デジタル減衰とアナログゲインに分割して使用します:

Volume結果
0−8 dB(DAC 減衰)
80 dB(基準、DAC フルスケール)
9 … 17+1 dB … +9 dB のアナログ出力ゲイン

0 から 8 までは DAC が 0.5 dB ステップ((8 − volume) × 2)で減衰します。9 から 17 では DAC は 0 dB のまま、アナログ出力段が volume − 8 dB だけブーストされます。

レジスタマップ​

レジスタアドレス用途
ページ制御0x00レジスタページ 0 を選択
左 DAC 音量0x2Bデジタル減衰、1 ステップあたり 0.5 dB
右 DAC 音量0x2Cデジタル減衰、1 ステップあたり 0.5 dB
HPLOUT レベル0x33ヘッドホン左出力
HPROUT レベル0x41ヘッドホン右出力
LEFT_LOP レベル0x56ラインアウト左
RIGHT_LOP レベル0x5Dラインアウト右

出力レジスタのビット:D7–D4 = アナログゲイン(0〜9 dB)、D3 = ミュート解除、D0 = 電源オン。ヘッドホン用レジスタでは、電源オフ時に高インピーダンスとする D2 も設定するため、下位 4 ビットの固定値として 0x0D を使用し、ラインアウト用レジスタでは 0x09 を使用します。

動作概要​

  1. setup() は I2C を 100 kHz で開始し、AIC3104 のページ制御レジスタに書き込んだ後、両方の DAC チャンネルを 0 dB に設定し、ヘッドホン出力とライン出力を 0 dB でミュート解除/電源オンにします。
  2. setVolume() は要求されたレベルを DAC 減衰値とアナログゲイン値に分割し、4 つすべての出力レジスタに書き込んだうえで、確認のため左 DAC レジスタを読み戻します。
  3. I2S は 48 kHz、ステレオ、32 ビットで TX_MODE を開始し、XIAO をスレーブとして XVF3800 が BCLK と LRCLK を駆動します。
  4. SineWaveGenerator が 32 ビットフルスケールの 25 % レベルで 440 Hz トーンを生成し、GeneratedSoundStream でラップして StreamCopy により I2S へコピーします。
  5. loop() はトーンの再生を継続しつつ、シリアルモニタからの 1 文字コマンドを処理します。

シリアルコマンド​

キー動作
+音量を上げる(最大 17 まで)
-音量を下げる(最小 0 まで)
mヘッドホン出力とライン出力を切り替える
v現在の音量を表示する
hコマンド一覧を表示する

コード​

#include <Wire.h>
#include "AudioTools.h"

// =====================================================
// AIC3104 I2C CONFIGURATION
// =====================================================

#define AIC3104_ADDR 0x18

#define I2C_SDA_PIN 5
#define I2C_SCL_PIN 6

// Register addresses
#define AIC3104_PAGE_CTRL 0x00

#define AIC3104_LEFT_DAC_VOLUME 0x2B
#define AIC3104_RIGHT_DAC_VOLUME 0x2C

#define AIC3104_HPLOUT_LEVEL 0x33
#define AIC3104_HPROUT_LEVEL 0x41

#define AIC3104_LEFT_LOP_LEVEL 0x56
#define AIC3104_RIGHT_LOP_LEVEL 0x5D


// =====================================================
// I2S CONFIGURATION
// =====================================================

#define SAMPLE_RATE 48000
#define CHANNELS 2
#define BITS 32

AudioInfo info(SAMPLE_RATE, CHANNELS, BITS);

// 440Hz sine wave, 25% of 32-bit full scale
SineWaveGenerator<int32_t> sineWave(0x20000000);

GeneratedSoundStream<int32_t> sound(sineWave);

I2SStream out;
StreamCopy copier(out, sound);


// =====================================================
// VOLUME CONFIGURATION
// =====================================================

// Volume: 0 to 17
// 0 = -8dB
// 8 = 0dB
// 17 = +9dB

int volume = 8;

// true = HPLOUT / HPROUT
// false = LEFT_LOP / RIGHT_LOP

bool useHPOUT = true;


// =====================================================
// AIC3104 REGISTER WRITE
// =====================================================

bool aic3104_reg_write(uint8_t reg, uint8_t val) {

Wire.beginTransmission(AIC3104_ADDR);

Wire.write(reg);
Wire.write(val);

uint8_t error = Wire.endTransmission();

if (error != 0) {

Serial.printf(
"I2C WRITE ERROR: Register 0x%02X, Error %u\n",
reg,
error
);

return false;
}

return true;
}


// =====================================================
// AIC3104 REGISTER READ
// =====================================================

bool aic3104_reg_read(uint8_t reg, uint8_t &value) {

Wire.beginTransmission(AIC3104_ADDR);

Wire.write(reg);

if (Wire.endTransmission(false) != 0) {
return false;
}

if (Wire.requestFrom(
(uint8_t)AIC3104_ADDR,
(uint8_t)1
) != 1) {

return false;
}

value = Wire.read();

return true;
}


// =====================================================
// INITIALIZE AIC3104
// =====================================================

bool setupAIC3104() {

Serial.println("\nInitializing AIC3104...");

// Select register page 0
if (!aic3104_reg_write(AIC3104_PAGE_CTRL, 0x00)) {
return false;
}

// DAC digital volume: 0dB, unmuted
if (!aic3104_reg_write(AIC3104_LEFT_DAC_VOLUME, 0x00))
return false;

if (!aic3104_reg_write(AIC3104_RIGHT_DAC_VOLUME, 0x00))
return false;

// HPLOUT / HPROUT:
// 0dB, unmuted, powered up
if (!aic3104_reg_write(AIC3104_HPLOUT_LEVEL, 0x0D))
return false;

if (!aic3104_reg_write(AIC3104_HPROUT_LEVEL, 0x0D))
return false;

// LEFT_LOP / RIGHT_LOP:
// 0dB, unmuted, powered up
// Bit 2 is reserved; write it as zero.
if (!aic3104_reg_write(AIC3104_LEFT_LOP_LEVEL, 0x09))
return false;

if (!aic3104_reg_write(AIC3104_RIGHT_LOP_LEVEL, 0x09))
return false;

Serial.println("AIC3104 initialized successfully");

return true;
}


// =====================================================
// SET OUTPUT ANALOG GAIN
// =====================================================

bool setOutputGain(uint8_t gain) {

gain = constrain(gain, 0, 9);

// Reset inactive output to 0dB.
// Keep active output at requested gain.

uint8_t hpGain =
useHPOUT ? gain : 0;

uint8_t lopGain =
useHPOUT ? 0 : gain;

// HP registers:
// D7-D4 = 0 to +9dB
// D3 = 1 (unmuted)
// D2 = 1 (high impedance when powered down)
// D0 = 1 (powered up)

uint8_t hpValue =
(hpGain << 4) | 0x0D;

// LOP registers:
// D7-D4 = 0 to +9dB
// D3 = 1 (unmuted)
// D2 = 0 (reserved)
// D0 = 1 (powered up)

uint8_t lopValue =
(lopGain << 4) | 0x09;

bool ok = true;

ok &= aic3104_reg_write(
AIC3104_HPLOUT_LEVEL,
hpValue
);

ok &= aic3104_reg_write(
AIC3104_HPROUT_LEVEL,
hpValue
);

ok &= aic3104_reg_write(
AIC3104_LEFT_LOP_LEVEL,
lopValue
);

ok &= aic3104_reg_write(
AIC3104_RIGHT_LOP_LEVEL,
lopValue
);

return ok;
}


// =====================================================
// SET VOLUME
// =====================================================

void setVolume(int vol) {

vol = constrain(vol, 0, 17);

volume = vol;

uint8_t dacValue;
uint8_t analogGain;

if (vol <= 8) {

// DAC attenuation:
// Volume 0 = -8dB
// Volume 8 = 0dB

int attenuationDB = 8 - vol;

// AIC3104 DAC step = 0.5dB
dacValue = attenuationDB * 2;

analogGain = 0;

} else {

// DAC at 0dB
dacValue = 0;

// Analog boost: +1dB to +9dB
analogGain = vol - 8;
}

// Update left and right DAC
bool ok = true;

ok &= aic3104_reg_write(
AIC3104_LEFT_DAC_VOLUME,
dacValue
);

ok &= aic3104_reg_write(
AIC3104_RIGHT_DAC_VOLUME,
dacValue
);

// Update analog output gain
ok &= setOutputGain(analogGain);

if (!ok) {

Serial.println("Volume update failed!");
return;
}

// Debug output

Serial.println("\n------------------------------");

Serial.printf(
"Volume level: %d / 17\n",
volume
);

if (vol <= 8) {

Serial.printf(
"DAC attenuation: -%d dB\n",
8 - vol
);

} else {

Serial.printf(
"Analog output gain: +%d dB\n",
analogGain
);
}

Serial.printf(
"Output mode: %s\n",
useHPOUT ? "HPLOUT" : "LINE OUT"
);

Serial.printf(
"DAC register: 0x%02X\n",
dacValue
);

uint8_t readValue;

if (aic3104_reg_read(
AIC3104_LEFT_DAC_VOLUME,
readValue
)) {

Serial.printf(
"DAC readback: 0x%02X\n",
readValue
);
}

Serial.println("------------------------------");
}


// =====================================================
// SERIAL CONTROL
// =====================================================

void handleSerial() {

while (Serial.available()) {

char c = Serial.read();

if (c == '+') {

if (volume < 17) {
setVolume(volume + 1);
} else {
Serial.println("Maximum volume reached");
}

} else if (c == '-') {

if (volume > 0) {
setVolume(volume - 1);
} else {
Serial.println("Minimum volume reached");
}

} else if (c == 'm' || c == 'M') {

useHPOUT = !useHPOUT;

setVolume(volume);

} else if (c == 'v' || c == 'V') {

Serial.printf(
"Current volume: %d / 17\n",
volume
);

} else if (c == 'h' || c == 'H') {

Serial.println("\nCommands:");
Serial.println("+ : Increase volume");
Serial.println("- : Decrease volume");
Serial.println("m : Switch HP / LINE output");
Serial.println("v : Show current volume");
Serial.println("h : Show help");
}
}
}


// =====================================================
// SETUP
// =====================================================

void setup() {

Serial.begin(115200);

while (!Serial);

Serial.println("\n================================");
Serial.println("ReSpeaker 44mm Volume Test");
Serial.println("XVF3800 + AIC3104");
Serial.println("================================");

// Initialize I2C
Wire.begin(
I2C_SDA_PIN,
I2C_SCL_PIN
);

Wire.setClock(100000);

delay(100);

Serial.println("I2C initialized");

// Initialize AIC3104
if (!setupAIC3104()) {

Serial.println("AIC3104 initialization FAILED!");

while (true) {
delay(1000);
}
}

// Set starting volume
setVolume(volume);

// Initialize I2S output
auto config = out.defaultConfig(TX_MODE);

config.copyFrom(info);

config.pin_bck = 8; // D9 / GPIO8
config.pin_ws = 7; // D8 / GPIO7
config.pin_data = 44; // D7 / GPIO44

// XVF3800 provides I2S clocks
config.is_master = false;

if (!out.begin(config)) {

Serial.println("I2S initialization FAILED!");

while (true) {
delay(1000);
}
}

// Start 440Hz sine wave
sineWave.begin(info, N_A4);

Serial.println("\nI2S started");
Serial.println("440Hz test tone playing");

Serial.println("\nVolume control ready:");
Serial.println("+ : Increase volume");
Serial.println("- : Decrease volume");
Serial.println("m : Switch HP / LINE output");
Serial.println("v : Show current volume");
Serial.println("h : Show help");
}


// =====================================================
// LOOP
// =====================================================

void loop() {

// Continuously transmit 440Hz test tone
copier.copy();

// Check serial commands
handleSerial();
}

期待される出力​

================================
ReSpeaker 44mm Volume Test
XVF3800 + AIC3104
================================
I2C initialized

Initializing AIC3104...
AIC3104 initialized successfully

------------------------------
Volume level: 8 / 17
Output mode: HPLOUT
DAC register: 0x00
DAC readback: 0x00
------------------------------

I2S started
440Hz test tone playing

Volume control ready:
+ : Increase volume
- : Decrease volume
m : Switch HP / LINE output
v : Show current volume
h : Show help

Send + to step up. Above level 8 the log switches to Analog output gain: +N dB:

------------------------------
Volume level: 10 / 17
Analog output gain: +2 dB
Output mode: HPLOUT
DAC register: 0x00
DAC readback: 0x00
------------------------------

トラブルシューティング​

I2C WRITE ERROR: Register 0x.., Error 2​

AIC3104 が 0x18 で応答していません。D4(GPIO5)の SDA と D5(GPIO6)の SCL を確認し、I2S ファームウェアが動作していることを確認してから、ボードの電源を入れ直してください。

AIC3104 initialization FAILED!​

コーデックがページ選択書き込みを受け付けませんでした。通常は、I2C バスが別のマスターに保持されているか、ボードがまだ起動中です — setupAIC3104() の前に短いディレイを追加して、再試行してください。

トーンが出ないが、シリアルログは正しい​

  • スピーカーまたはヘッドホンが JST / 3.5 mm 出力に接続されていることを確認します。
  • 440 Hz のトーンはフルスケールの 25 % で生成されるため、レベル 8 では意図的に小さな音量になっています。
  • m を押して、別の出力経路(ヘッドホン出力とライン出力)を試します。

数秒後に音量が元に戻ってしまう​

XVF3800 ファームウェアまたはオンボードの + / - ボタンが、同じ AIC3104 レジスタに書き込みを行います。ボタンには触れずに XIAO からレベルを設定するか、コーデックを XIAO の制御下のみに保ってください。

m で切り替えるときにクリック音やポップノイズがする​

両方の出力ステージは、変更のたびに再設定されます。これは AIC3104 では想定された動作です。切り替える前に音量を下げて、ノイズを軽減してください。

技術サポートと製品ディスカッション​

弊社製品をお選びいただきありがとうございます。弊社は、製品をできるだけスムーズにご利用いただけるよう、さまざまなサポートを提供しています。お好みやニーズに合わせてお選びいただけるよう、複数のコミュニケーションチャネルをご用意しています。

Loading Comments...