前回までのあらすじ
以下の記事で構築した M5StampS3 + 3.2 インチ LCD (ILI9341) の MSX エミュレータで、今回は I2S 対応 DAC モジュール UDA1334A を接続して AY-3-8910 (PSG) の音声再生に対応しました。本書では、それに際して必要だった部品、組み立て方法、実装方法について紹介します。
事前に以下の記事を確認しておくことをオススメします。
必要な部品や工具
本書で解説する内容に対応するには、UDA1334A と 5 本のジャンパー線(♂♂)が必要です。
UDA1334A は、以前はスイッチサイエンスやマルツオンラインで購入できましたが、現時点(2023.07.29時点)ではどちらも品切れ状態で、販売終了となっているので国内ではもう手に入りません。
ですが、Aliexpress には在庫があるので、私は Aliexpress で予備ストックを含めて(LCD購入時に一緒に)輸入しておきました。
また、ピンヘッダのはんだ付けは今回は最初から必須なので、ハンダセットも必要です。
ピンヘッダのはんだ付け
M5StampS3 の細かなピンヘッダ(1.27mmピッチ)のはんだ付けに成功した猛者にとっては、ヌルすぎるイージーゲームですね・・・
あいも変わらず、きったねーなーw
まぁ、ショートせずちゃんと通電できて動くことが最重要なので、見た目は気にしなくて良いと思います。
LCD の配線修正
DAC の取り付け作業をする前に空きの GPIO ポートを確認したところ、若干心もとなかったので、冗長に設定した LCD の一部配線を修正して空き GPIO ポートを確保しておきます。
これまで、LCD の LED
と RESET
に GPIO (5と7) を無駄に割り当てていましたが、LED
は 3V3
、RESET
は EN
に配線することで2ポート節約します。
LCD (ILI9341) | M5StampS3 | 用途 |
---|---|---|
VCC |
3V3 |
電源給電 |
GND |
GND |
グランド |
CS |
1 (GPIO) |
LCD チップ選択有効化の信号 |
RESET |
EN ※変更
|
リセット信号 |
DC |
3 (GPIO) |
データ/レジスタ選択の信号 |
SDI (MOSI) |
13 SDA |
I2C (Stamp→LCD方向のデータ送信) |
SCK |
15 SCL |
I2C (クロック同期) |
LED |
3V3 ※変更
|
バックライト |
SDO (MISO) |
配線なし | LCD→Stamp方向のデータ送信は不要なため |
T_CS |
配線なし | タッチ機能は利用しないため |
T_DIN |
配線なし | タッチ機能は利用しないため |
T_DO |
配線なし | タッチ機能は利用しないため |
T_IRQ |
配線なし | タッチ機能は利用しないため |
UDA1334Aの配線
UDA1334Aには色々なポートがありますが、音を鳴らすために必要な最低限のポートは、VIN
、GND
、WSEL
、DIN
、BCLK
の 5 つだけです。
DAC (UDA1334A) | M5StampS3 | 用途 |
---|---|---|
VIN |
5V |
電源給電 |
GND |
GND |
グランド |
WSEL |
9 |
I2S (チャネルセレクタ)LOW = Left, HIGH = Right) |
DIN |
7 |
I2S (データ送信) |
BCLK |
5 |
I2S (クロック同期) |
モノラル限定なら WSEL
は必ずしも GPIO である必要はないかもと思い、LEDと同様3V3に接続して常に HIGH にしておけば良いのでは?と安直に考えたのですが、それを実施してみたところ UDA1334A
から一切音が出なくなってしまいました。(元に戻しても治らないので過電圧か何かで壊れたかも?)
WSELはおとなしくGPIOに繋いでおくのが吉のようです
単価が安かったのでストック多めに買っておいて助かった...
なお、VIN
は 5V
に接続しなければいけません。
3V3
だと強かにノイズが入ります(説明書には3.3〜5Vとあったので3.3Vでも大丈夫かと思ったのですがダメでした...)
WSEL
、DIN
、BCLK
は適当な空いている GPIO に割り当てています。
UDA1334A にはステレオミニプラグインがついているので、ダイソーの300円スピーカーとかを付けておけば良いと思います。(私は持ってないので昔のiPhoneのイヤフォンを付けてますが)
Audio クラスの実装
以下、前述の配線で 44100Hz, 16bit, 1ch (モノラル) の Raw PCM Streaming を行うための初期化処理(Audio::begin
)とバッファリング処理(Audio::write
)を示します。
class Audio
{
private:
static constexpr i2s_port_t i2sNum = I2S_NUM_0;
static constexpr int sampleRate = 44100;
public:
void begin()
{
// I2S config
i2s_config_t config;
memset(&config, 0, sizeof(i2s_config_t));
config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
config.sample_rate = this->sampleRate;
config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
config.communication_format = I2S_COMM_FORMAT_STAND_MSB;
config.dma_buf_count = 4;
config.dma_buf_len = 1024;
config.tx_desc_auto_clear = true;
// I2S pin config
i2s_pin_config_t pinConfig;
memset(&pinConfig, ~0u, sizeof(i2s_pin_config_t));
pinConfig.bck_io_num = GPIO_NUM_5; // BCLK に配線したGPIO番号を指定
pinConfig.ws_io_num = GPIO_NUM_9; // WSEL に配線したGPIO番号を指定
pinConfig.data_out_num = GPIO_NUM_7; // DIN に配線したGPIO番号を指定
pinConfig.data_in_num = I2S_PIN_NO_CHANGE;
// Setup I2S
if (ESP_OK != i2s_driver_install(this->i2sNum, &config, 0, nullptr)) {
i2s_driver_uninstall(this->i2sNum);
i2s_driver_install(this->i2sNum, &config, 0, nullptr);
}
i2s_set_pin(this->i2sNum, &pinConfig);
i2s_set_sample_rates(this->i2sNum, this->sampleRate);
i2s_set_clk(this->i2sNum, this->sampleRate, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
i2s_zero_dma_buffer(this->i2sNum);
i2s_start(this->i2sNum);
}
inline void write(int16_t* buf, size_t bufSize)
{
size_t wrote;
i2s_write(this->i2sNum, buf, bufSize, &wrote, portMAX_DELAY);
vTaskDelay(2);
}
};
再生例としてはこんな感じです。