初めてのRaspberry Pi Pico ㉙ C言語 I2S その1

　I2Sのサンプルにsine_waveがあります。これを動かします。
###環境を整える
　第8回で、pico/worksのディレクトリで開発をするように環境を整えました。その延長の設定です。I2Sのライブラリは、/pico/pic-extrasに入っているので、そのためのパスを通します。

　/home/pi/picoにworksフォルダを作っています。
　CmakeLists.txt、pico_sdk_import.cmakeファイルとcmakeフォルダが入っています。ここに、**pico_extras_import.cmake**ファイルを追加します。このファイルは、/home/pi/pico/pico-playgroundに置いてあったものをコピーしてきます。
　pico_sdk_import.cmakeとpico_extras_import.cmakeは中身はそのままです。CmakeLists.txtの内容です。

```shell:CmakeLists.txt
cmake_minimum_required(VERSION 3.12)

# Pull in SDK (must be before project)
include(pico_sdk_import.cmake)

# We also need PICO EXTRAS
include(pico_extras_import.cmake)

project(pico_examples C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR})

# Initialize the SDK
pico_sdk_init()

# Add blink example
add_subdirectory(blink)
add_subdirectory(cmake)
add_subdirectory(LED7seg_74HC595)
add_subdirectory(bus_scan)
add_subdirectory(lcd_1602_i2c)
add_subdirectory(CRC8)
add_subdirectory(adt7410)
add_subdirectory(hello_pwm)
add_subdirectory(mcp3201)
add_subdirectory(mcp3202)
add_subdirectory(mcp3204)
add_subdirectory(mcp3208)
add_subdirectory(mcp3208x2)
add_subdirectory(LTC2461)
add_subdirectory(ads1110)
add_subdirectory(ads1115)
add_subdirectory(ads122u04)
add_subdirectory(pwm2)
add_subdirectory(sine_wave)
```

　追加したのは、

```
# We also need PICO EXTRAS
include(pico_extras_import.cmake)
```

と、最後に、今回作成するsine_waveフォルダの記述です。

```
add_subdirectory(sine_wave)
```

　/home/pi/pico/pico-playground/audioから、sine_waveフォルダをコピーしてきます。
![i2s101a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/928e718c-53c0-50a8-3fed-3365660aa57f.png)

###関連のソースの場所
　/home/pi/pico/pico-extras/src/rp2_common/pico_audio_i2sに、修正することがあるかもしれないプログラムが二つ置いてあります。

- LRCLK(WS)とBCKの関係を記述したaudio_i2s.pioがあります。16ビット、32fsで波形を作っています。最近のハイレゾは、32（24）ビット、64fsです。

- audio_i2s.cは、system_clock_frequency（システム・クロック。通常125MHz）とsample_freq（サンプリング周波数）の関係から、BCKのクロック周波数を求めています。32fsですから、ハイレゾでは使えません。

###フォーマット
　I2Sには3種類のフォーマットがあります。Picoのドキュメントの中にどれが採用されているかは、記述が見つけられませんでした。
　[Hardware design with RP2040](https://datasheets.raspberrypi.org/rp2040/hardware-design-with-rp2040.pdf)のサンプル回路図「Figure 24. Schematic section showing the connections to Raspberry Pi Pico」で使われているPCM5101AではFMT端子がGNDに落ちているので、I2Sフォーマットと思われます。
　しかし、オシロスコープで見ると、LRCLKの切り替わりの直後の１BCKサイクルのウェイトがないように見えます。audio_i2s.pioのソースでもそのような記述がないように見えます。
![i2s102.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/b15e91b4-4e49-35bb-5bb5-26ffe29b9e55.png)

###用意したハードウェア
　たぶんテレビ用だと思いますが、NXPのUDA1334Aを搭載したボードを使いました。[スイッチサイエンス](https://www.switch-science.com/catalog/3572/)でもAdafruitの製品を取り扱っています。

<img width="280" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/97bfb0dd-538c-dac5-28e7-2a4d3e28a3dd.png">

###接続

|UDA1334Aボードの端子|	Picoの端子(GPIO)	|名称|
|------------|------------|:-------------:|:------|
|Vin|	3.3V|	3.3V|
|Vo|	-|	-|
|GND|	GND|GND|
|Wsel|	GP27|	LRCK|
|Din|	GP28|	Data|
|BCK|	GP26|	BCK|
|SCLK|	-|	-|
|FS0|	3.3V|	3.3V|
|MUTE|	-|	-|
|FS1|	GND|	GND|
|PLL|	-|	-|
|DEEM|	-|	-|

　FS0、FS1の設定は、[データシート](https://cdn-shop.adafruit.com/product-files/3678/UDA1334ATS.pdf)から、I2SではなくLSB-justified 16 bits inputを選択しました。FS0、FS1の両方をGNDに落としたI2Sフォーマットでは、ノイズだけがでました。

　[Hardware design with RP2040](https://datasheets.raspberrypi.org/rp2040/hardware-design-with-rp2040.pdf)のサンプル回路図「Figure 24. Schematic section showing the connections to Raspberry Pi Pico」で使われているPCM5101Aでは、

- GP28	LRCK
- GP26	Data
- GP27	BCK

となっています。しかし、/home/pi/pico/pico-extras/src/rp2_common/pico_audio_i2s/include/pico/audio_i2s.hでは、

- #define PICO_AUDIO_I2S_DATA_PIN 28
- #define PICO_AUDIO_I2S_CLOCK_PIN_BASE 26

　したがって、次の接続にしました。
- GP28	Data
- GP26	BCK
- GP27	LRCK


###プログラム
　worksにコピーしてきたsine_waveフォルダには、CMakeLists.txtとsine_wave.cが入っています。どちらも修正します。

```cpp:CMakeLists.txt
add_executable(sine_wave_i2s
        sine_wave.c
        )

target_link_libraries(sine_wave_i2s PRIVATE
        pico_stdlib
        pico_audio_i2s
        )

target_compile_definitions(sine_wave_i2s PRIVATE
        # compile time configuration of I2S
        PICO_AUDIO_I2S_MONO_INPUT=1
        #define for our example code
        USE_AUDIO_I2S=1
        )
# create map/bin/hex file etc.
pico_add_extra_outputs(sine_wave_i2s)
```

　サンプリング周波数は24kHzです。

```cpp:sine_wave.c
/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include <math.h>

#include "hardware/clocks.h"
#include "hardware/structs/clocks.h"

#include "pico/stdlib.h"
#include "pico/audio_i2s.h"

#define SINE_WAVE_TABLE_LEN 2048
#define SAMPLES_PER_BUFFER 256

static int16_t sine_wave_table[SINE_WAVE_TABLE_LEN];

struct audio_buffer_pool *init_audio() {

    static audio_format_t audio_format = {
            .format = AUDIO_BUFFER_FORMAT_PCM_S16,

            .sample_freq = 24000 ,

            .channel_count = 1,
    };

    static struct audio_buffer_format producer_format = {
            .format = &audio_format,
            .sample_stride = 2
    };

    struct audio_buffer_pool *producer_pool = audio_new_producer_pool(&producer_format, 3,
                                                                      SAMPLES_PER_BUFFER); // todo correct size
    bool __unused ok;
    const struct audio_format *output_format;

    struct audio_i2s_config config = {
            .data_pin = PICO_AUDIO_I2S_DATA_PIN,
            .clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE,
            .dma_channel = 0,
            .pio_sm = 0,
    };

    output_format = audio_i2s_setup(&audio_format, &config);
    if (!output_format) {
        panic("PicoAudio: Unable to open audio device.\n");
    }

    ok = audio_i2s_connect(producer_pool);
    assert(ok);
    audio_i2s_set_enabled(true);

    return producer_pool;

}

int main() {

//    set_sys_clock_48mhz();

    stdio_init_all();
    printf("\nstart I2S wave\n");
    for (int i = 0; i < SINE_WAVE_TABLE_LEN; i++) {
        sine_wave_table[i] = 32767 * cosf(i * 20 * (float) (M_PI / SINE_WAVE_TABLE_LEN));
    }

    struct audio_buffer_pool *ap = init_audio();
    uint32_t step = 0x200000;
    uint32_t pos = 0;
    uint32_t pos_max = 0x10000 * SINE_WAVE_TABLE_LEN;
    uint vol = 128;
    while (true) {
//#if USE_AUDIO_PWM
//        enum audio_correction_mode m = audio_pwm_get_correction_mode();
//#endif
        int c = getchar_timeout_us(0);
        if (c >= 0) {
            if (c == '-' && vol) vol -= 4;
            if ((c == '=' || c == '+') && vol < 255) vol += 4;
            if (c == '[' && step > 0x10000) step -= 0x10000;
            if (c == ']' && step < (SINE_WAVE_TABLE_LEN / 16) * 0x20000) step += 0x10000;
            if (c == 'q') break;

            printf("vol = %d, step = %d      \r", vol, step >> 16);

        }
        struct audio_buffer *buffer = take_audio_buffer(ap, true);
        int16_t *samples = (int16_t *) buffer->buffer->bytes;
        for (uint i = 0; i < buffer->max_sample_count; i++) {
            samples[i] = (vol * sine_wave_table[pos >> 16u]) >> 8u;
            pos += step;
            if (pos >= pos_max) pos -= pos_max;
        }
        buffer->sample_count = buffer->max_sample_count;
        give_audio_buffer(ap, buffer);
    }
    puts("\n");
    return 0;
}
```

　sin波の周波数は、

```
sine_wave_table[i] = 32767 * cosf(i * 20 * (float) (M_PI / SINE_WAVE_TABLE_LEN));
```

　iに乗じている20を変更すると変わります。volの値は、~~修正時にプログラムのどこかが間違っていて機能しません。~~ ターミナルで、``+ -``などのキーに応じて変化します。

 　ターミナルで、~/pico/works/buildに降りて、``cmake ..``します。``cd sine_wave``でさらに降りて``make -j4``でビルドします。ソースを変更したときは再度``make -j4``で新しいコードができます。
　第6回で作ったResetボタンを使います。Resetボタンを押したまま、BOOTSELボタンを押し、Resetボタンを放してから、BOOTSELボタンを放します。これでRPI-RP2ディスクがマウントができます。
　/home/pi/pico/works/build/sine_waveフォルダにできているsine_wave_i2s.uf2をRPI-RP2ディスクにドロップすると、プログラムが実行されます。