M5StampS3 + UDA1334A (I2S/DAC) で MSX の音声出力に対応

# 前回までのあらすじ

以下の記事で構築した M5StampS3 + 3.2 インチ LCD (ILI9341) の MSX エミュレータで、今回は I2S 対応 DAC モジュール UDA1334A を接続して AY-3-8910 (PSG) の音声再生に対応しました。本書では、それに際して必要だった部品、組み立て方法、実装方法について紹介します。

_事前に以下の記事を確認しておくことをオススメします。_

https://qiita.com/suzukiplan/items/b5882449fdbf571b4e80

https://qiita.com/suzukiplan/items/e7f66d1b8ae7d6c7e03e

https://qiita.com/suzukiplan/items/84d2d5fcdf3fc303a124

# 必要な部品や工具

本書で解説する内容に対応するには、UDA1334A　と 5 本のジャンパー線（♂♂）が必要です。

UDA1334A　は、以前はスイッチサイエンスやマルツオンラインで購入できましたが、現時点（2023.07.29時点）ではどちらも品切れ状態で、販売終了となっているので国内ではもう手に入りません。

https://www.marutsu.co.jp/pc/i/27066648/

ですが、Aliexpress には在庫があるので、私は Aliexpress で予備ストックを含めて（LCD購入時に一緒に）輸入しておきました。

https://ja.aliexpress.com/item/1005005697892788.html

また、ピンヘッダのはんだ付けは今回は最初から必須なので、ハンダセットも必要です。

# ピンヘッダのはんだ付け

M5StampS3 の細かなピンヘッダ（1.27mmピッチ）のはんだ付けに成功した猛者にとっては、ヌルすぎるイージーゲームですね・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2161417/327aacc7-eed3-0862-c6e1-8b60fb380667.png)

_あいも変わらず、きったねーなーｗ_

まぁ、ショートせずちゃんと通電できて動くことが最重要なので、見た目は気にしなくて良いと思います。

# 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` <sup>__※変更__</sup>|リセット信号|
|`DC`|`3` (GPIO)|データ/レジスタ選択の信号|
|`SDI (MOSI)`|`13 SDA`|I2C (Stamp→LCD方向のデータ送信)|
|`SCK`|`15 SCL`|I2C (クロック同期)|
|`LED`|`3V3` <sup>__※変更__</sup>|バックライト|
|`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 (チャネルセレクタ)<BR>`LOW` = Left, `HIGH` = Right)|
|`DIN`|`7`|I2S (データ送信)|
|`BCLK`|`5`|I2S (クロック同期)|

:::note warn
モノラル限定なら `WSEL` は必ずしも GPIO である必要はないかもと思い、LEDと同様3V3に接続して常に HIGH にしておけば良いのでは？と安直に考えたのですが、それを実施してみたところ `UDA1334A` から一切音が出なくなってしまいました。（元に戻しても治らないので過電圧か何かで壊れたかも？）

__WSELはおとなしくGPIOに繋いでおくのが吉のようです__

_単価が安かったのでストック多めに買っておいて助かった..._
:::

なお、`VIN` は `5V` に接続しなければいけません。

:::note warn
`3V3` だと強かにノイズが入ります（説明書には3.3〜5Vとあったので3.3Vでも大丈夫かと思ったのですがダメでした...）
:::

`WSEL`、`DIN`、`BCLK` は適当な空いている GPIO に割り当てています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2161417/ad3df859-9787-892b-7e4b-b93a8550f717.png)
だいぶ良い感じにゴチャゴチャしてきました。

UDA1334A にはステレオミニプラグインがついているので、ダイソーの300円スピーカーとかを付けておけば良いと思います。（私は持ってないので昔のiPhoneのイヤフォンを付けてますが）

# Audio クラスの実装

以下、前述の配線で 44100Hz, 16bit, 1ch (モノラル) の Raw PCM Streaming を行うための初期化処理（`Audio::begin`）とバッファリング処理（`Audio::write`)を示します。

```c++
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);
    }
};
```

再生例としてはこんな感じです。

https://www.youtube.com/watch?v=JWPvyaTjD2k