サンプルプログラムにボタンを追加する

前回はADFをインストールしサンプルプログラムを変更してMP3ファイルを再生することが出来ました。今回は、回路にボタンを追加してボタンを押したら曲を再生するように変更して行きます。

ボタンをどう配線するの

Espressif社の製品カードにはボタンが付いていて、選曲やスタートストップをそのボタンで操作するよになっている様です。製品カードを持っていないので配線の仕方が分かりません。Webで探して、”LYRAT_V4.3” の回路を見つけました。

左が”LYRAT_V4.3”ボタン周りの回路。ボタンを押す前はHigh、押してLowになる回路です。これを参考に前回の回路のGPIO36に上図右の様にボタンを追加しました。

プロジェクトの構成

ボタンを追加する為に前回削除した2つのファイル、board.c, board_def.h を復活させます。これらにボタンの情報を追加して行くのですが、それ以外のファイルも変更しているので順を追って説明して行きます。


- ~esp/play_mp3_control/
      - CMakeLists.txt
      - components/
                   - my_board/
                              - CMakeLists.txt
                              - Kconfig.projbuild
                              - my_board_v1_0/
                                             - board.h
                                             - board.c
                                             - board_def.h
                                             - board_pins_config.c
      - main/
                   - CMakeLists.txt
                   - music-16b-2c-44100hz.mp3
                   - play_mp3_control_example.c

先ずは、components/my_board/my_board_v1_0/フォルダー

このフォルダーの、board_def.h でボタン用GPIOの登録を行っています。サンプルプロジェクトに有ったファイルを元に必要な部分のみを書きました。

board_def.h

#ifndef _AUDIO_BOARD_DEFINITION_H_
#define _AUDIO_BOARD_DEFINITION_H_

#define BUTTON_PLAY_ID    GPIO_NUM_36   /* You need to define the GPIO pins of your board */

#define INPUT_KEY_NUM     1             /* You need to define the number of input buttons on your board */

#define INPUT_KEY_DEFAULT_INFO() {                      \
        .type = PERIPH_ID_BUTTON,                       \
        .user_id = INPUT_KEY_USER_ID_PLAY,              \
        .act_id = BUTTON_PLAY_ID,                       \
}

#endif
  • 4行:define  BUTTON_PLAY_ID  GPIO_NUM_36
    • ここで、プログラム内で使用するIDとGPIOポートを宣言します。
    • IDを BUTTON_PLAY_ID 。 GPIOポートを 36としています。
  • 6行:define INPUT_KEY_NUM  1
    • 使用するGPIOポートの数を指定。
    • 今回はPLAY用ボタン1つなので、”1”を設定
  • 8行から:ここから登録するボタンのコンフィグの設定になります。
    • 9行: .type = PERIPH_ID_BUTTON, デジタルIOと指定。この他にADC等の指定も有ります。
    • 10行: .user_id = INPUT_KEY_USER_ID_PLAY, ユーザーID。今回は使いませんでした。
    • 11行: .act_id = BUTTON_PLAY_ID, 上記の宣言を使用するようです。 

このボタンに機能を持たせているのが、board.c ファイルです。

board.c

#include "esp_log.h"
#include "board.h"
#include "audio_mem.h"

#include "periph_button.h"

static const char *TAG = "AUDIO_BOARD";

esp_err_t audio_board_key_init(esp_periph_set_handle_t set)
{
    periph_button_cfg_t btn_cfg = {
        .gpio_mask = (1ULL << get_input_play_id()),  //Play BTN
    };
    esp_periph_handle_t button_handle = periph_button_init(&btn_cfg);
    AUDIO_NULL_CHECK(TAG, button_handle, return ESP_ERR_ADF_MEMORY_LACK);
    esp_err_t ret = ESP_OK;
    ret = esp_periph_start(set, button_handle);

    return ret;
}

  • 9行以降:esp_err_t audio_board_key_init(esp_periph_set_handle_t set)
    • ボタンの初期設定を行っています。
    • 12行:ボタンコンフィグとして使用するボタン(GPIO36)ビットを持って
    • 14行:ボタン用ハンドルを製作します。
    • 15行:製作の確認
    • 17行:ペリフェラルハンドルのset にボタンを登録し、動作を開始。

board.c で使用している関数、get_input_play_id()は board_pins_config.c で定義しています。

board_pins_config.c

#include "esp_log.h"
#include "driver/gpio.h"
#include <string.h>
#include "board.h"
#include "audio_error.h"
#include "audio_mem.h"

static const char *TAG = "MY_BOARD_V1_0";

esp_err_t get_i2s_pins(i2s_port_t port, i2s_pin_config_t *i2s_config)
{
    AUDIO_NULL_CHECK(TAG, i2s_config, return ESP_FAIL);
    if (port == I2S_NUM_0) {
//        i2s_config->bck_io_num = GPIO_NUM_4;
//        i2s_config->ws_io_num = GPIO_NUM_13;
//        i2s_config->data_out_num = GPIO_NUM_16;
//        i2s_config->data_in_num = GPIO_NUM_39;

        i2s_config->bck_io_num = GPIO_NUM_26;
        i2s_config->ws_io_num = GPIO_NUM_25;
        i2s_config->data_out_num = GPIO_NUM_22;
    } else if (port == I2S_NUM_1) {
        i2s_config->bck_io_num = -1;
        i2s_config->ws_io_num = -1;
        i2s_config->data_out_num = -1;
        i2s_config->data_in_num = -1;
    } else {
        memset(i2s_config, -1, sizeof(i2s_pin_config_t));
        ESP_LOGE(TAG, "i2s port %d is not supported", port);
        return ESP_FAIL;
    }

    return ESP_OK;
}

esp_err_t i2s_mclk_gpio_select(i2s_port_t i2s_num, gpio_num_t gpio_num)
{
    if (i2s_num >= I2S_NUM_MAX) {
        ESP_LOGE(TAG, "Does not support i2s number(%d)", i2s_num);
        return ESP_ERR_INVALID_ARG;
    }
    if (gpio_num != GPIO_NUM_0 && gpio_num != GPIO_NUM_1 && gpio_num != GPIO_NUM_3) {
        ESP_LOGE(TAG, "Only support GPIO0/GPIO1/GPIO3, gpio_num:%d", gpio_num);
        return ESP_ERR_INVALID_ARG;
    }
    ESP_LOGI(TAG, "I2S%d, MCLK output by GPIO%d", i2s_num, gpio_num);
    if (i2s_num == I2S_NUM_0) {
        if (gpio_num == GPIO_NUM_0) {
            PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
            WRITE_PERI_REG(PIN_CTRL, 0xFFF0);
        } else if (gpio_num == GPIO_NUM_1) {
            PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3);
            WRITE_PERI_REG(PIN_CTRL, 0xF0F0);
        } else {
            PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2);
            WRITE_PERI_REG(PIN_CTRL, 0xFF00);
        }
    } else if (i2s_num == I2S_NUM_1) {
        if (gpio_num == GPIO_NUM_0) {
            PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
            WRITE_PERI_REG(PIN_CTRL, 0xFFFF);
        } else if (gpio_num == GPIO_NUM_1) {
            PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3);
            WRITE_PERI_REG(PIN_CTRL, 0xF0FF);
        } else {
            PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2);
            WRITE_PERI_REG(PIN_CTRL, 0xFF0F);
        }
    }
    return ESP_OK;
}

// play button
int8_t get_input_play_id(void)
{
    return BUTTON_PLAY_ID;
}

74行目以降に関数(int8_t get_input_play_id(void))の定義が有ります。BUTTON_PLAY_ID を返すのみの関数です。ちなみに BUTTON_PLAY_ID は GPIO_NUM_36(36) です。

board.h には新しく追加した関数を登録 21行に登録しています。

board.h

#ifndef _AUDIO_BOARD_H_
#define _AUDIO_BOARD_H_

#include "board_def.h"
#include "board_pins_config.h"
#include "esp_peripherals.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Initialize key peripheral
 *
 * @param set The handle of esp_periph_set_handle_t
 *
 * @return
 *     - ESP_OK, success
 *     - Others, fail
 */
esp_err_t audio_board_key_init(esp_periph_set_handle_t set);

#ifdef __cplusplus
}
#endif

#endif

次は、components/my_board/フォルダー

ここでは、CMakeLists.txt を変更します。COMPONENT_SRCS に ./my_board_v1_0/board.c を追加(9行)しています。

CMakeLists.txt

# Edit following two lines to set component requirements (see docs)
set(COMPONENT_REQUIRES)
set(COMPONENT_PRIV_REQUIRES esp_peripherals)

if(CONFIG_AUDIO_BOARD_CUSTOM)
message(STATUS "Current board name is " CONFIG_AUDIO_BOARD_CUSTOM)
list(APPEND COMPONENT_ADD_INCLUDEDIRS ./my_board_v1_0)
set(COMPONENT_SRCS
./my_board_v1_0/board.c
./my_board_v1_0/board_pins_config.c
)
endif()

register_component()

IF (IDF_VERSION_MAJOR GREATER 3)
idf_component_get_property(audio_board_lib audio_board COMPONENT_LIB)
set_property(TARGET ${audio_board_lib} APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${COMPONENT_LIB})

ELSEIF (IDF_VERSION_MAJOR EQUAL 3)
set_property(TARGET idf_component_audio_board APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES  $<TARGET_PROPERTY:${COMPONENT_TARGET},INTERFACE_INCLUDE_DIRECTORIES>)

ENDIF (IDF_VERSION_MAJOR GREATER 3)

最後に、main/フォルダー

ここでは、play_mp3_control_example.c を変更します。前回のプログラムに70行目以降を追加しています。

play_mp3_control_example.c

// Play mp3 file by audio pipeline

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "esp_log.h"
#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_mem.h"
#include "audio_common.h"
#include "i2s_stream.h"
#include "mp3_decoder.h"
#include "board.h"

static const char *TAG = "PLAY_FLASH_MP3_CONTROL";

static struct marker {
    int pos;
    const uint8_t *start;
    const uint8_t *end;
} file_marker;

// high rate mp3 audio
extern const uint8_t hr_mp3_start[] asm("_binary_music_16b_2c_44100hz_mp3_start");
extern const uint8_t hr_mp3_end[]   asm("_binary_music_16b_2c_44100hz_mp3_end");

int mp3_music_read_cb(audio_element_handle_t el, char *buf, int len, TickType_t wait_time, void *ctx)
{
    int read_size = file_marker.end - file_marker.start - file_marker.pos;
    if (read_size == 0) {
        return AEL_IO_DONE;
    } else if (len < read_size) {
        read_size = len;
    }
    memcpy(buf, file_marker.start + file_marker.pos, read_size);
    file_marker.pos += read_size;
    return read_size;
}

void app_main(void)
{
    audio_pipeline_handle_t pipeline;
    audio_element_handle_t i2s_stream_writer, mp3_decoder;

    ESP_LOGI(TAG, "[ 2 ] Create audio pipeline, add all elements to pipeline, and subscribe pipeline event");
    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    pipeline = audio_pipeline_init(&pipeline_cfg);
    mem_assert(pipeline);

    ESP_LOGI(TAG, "[2.1] Create mp3 decoder to decode mp3 file and set custom read callback");
    mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
    mp3_decoder = mp3_decoder_init(&mp3_cfg);
    audio_element_set_read_cb(mp3_decoder, mp3_music_read_cb, NULL);

    ESP_LOGI(TAG, "[2.2] Create i2s stream to write data to codec chip");
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.type = AUDIO_STREAM_WRITER;
    i2s_stream_writer = i2s_stream_init(&i2s_cfg);

    ESP_LOGI(TAG, "[2.3] Register all elements to audio pipeline");
    audio_pipeline_register(pipeline, mp3_decoder, "mp3");
    audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");

    ESP_LOGI(TAG, "[2.4] Link it together [mp3_music_read_cb]-->mp3_decoder-->i2s_stream-->[codec_chip]");
    const char *link_tag[2] = {"mp3", "i2s"};
    audio_pipeline_link(pipeline, &link_tag[0], 2);

    ESP_LOGI(TAG, "[ 3 ] Initialize peripherals");
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);

    ESP_LOGI(TAG, "[3.1] Initialize keys on board");
    audio_board_key_init(set);
	
    ESP_LOGI(TAG, "[ 4 ] Set up  event listener");
    audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
    audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);

    ESP_LOGI(TAG, "[4.2] Listening event from peripherals");
    audio_event_iface_set_listener(esp_periph_set_get_event_iface(set), evt);

    ESP_LOGI(TAG, "[5] Waiting button");
	audio_event_iface_msg_t msg;
  	audio_event_iface_listen(evt, &msg, portMAX_DELAY);

    ESP_LOGI(TAG, "[5.1] Start audio");
    file_marker.start = hr_mp3_start;
   	file_marker.end   = hr_mp3_end;
   	file_marker.pos = 0;
   	audio_pipeline_run(pipeline);

   	int flg = 1;
   	while(flg)
   	{
   		vTaskDelay(100 / portTICK_PERIOD_MS);
		if(audio_element_get_state(i2s_stream_writer) == AEL_STATE_FINISHED) 
			flg = 0;
    }

    ESP_LOGI(TAG, "[ 6 ] Stop audio_pipeline");
    audio_pipeline_terminate(pipeline);
    audio_pipeline_unregister(pipeline, mp3_decoder);
    audio_pipeline_unregister(pipeline, i2s_stream_writer);

    /* Terminate the pipeline before removing the listener */
    audio_pipeline_remove_listener(pipeline);

    /* Release all resources */
    audio_pipeline_deinit(pipeline);
    audio_element_deinit(i2s_stream_writer);
    audio_element_deinit(mp3_decoder);
}
  • 71行:デフォルトの値を使って
  • 72行:ペリフェラルハンドル set を作成
  • 75行:set を持ってボードの初期設定。ここでボタンハンドルが setに追加されました。
  • 78行:デフォルトのlistener値を使って
  • 79行:ハンドル evt を作成
  • 82行:evt を setのlistenerに設定。
  • 86行:audio_event_iface_listen(evt, &msg, portMAX_DELAY);
    • これで対象エレメントからのメッセージを受け取る事が出来ます。
    • 第一引数:audio_event_iface_handle_tのevt。
      • evtはボタンのリスナーなのでボタンイベントを受けれます
    • 第2引数:audio_event_iface_msg_t の&msg。 
      • ここにメッセージが入ります。メーッセージには
      • PERIPH_BUTTON_UNCHANGE = 0  No event
      • PERIPH_BUTTON_PRESSED    When button is pressed
      • PERIPH_BUTTON_RELEASE    When button is released
      • PERIPH_BUTTON_LONG_PRESSED  When button is pressed and kept for more than long_press_time_ms     
      • PERIPH_BUTTON_LONG_RELEASE  When button is released and event PERIPH_BUTTON_LONG_PRESSED happened
    • 第3引数:TickType_t wait_time
      • メッセージモニター時間。指定した時間の間イベントをモニターします
      • portMAX_DELAYですが、正確な時間は分かりませんがかなり長い時間モニターしています。
    • 今回はメッセージの判断はしていません。portMAX_DELAYでイベントが起こるまでウエイトを掛けた状態になります。
    • よってボタンを押したら次に進みます。
  • 以降は前回と同じ。

これでプログラムの変更が済みました。コンパイルして実行しましょう。

実行まで

前回からの引き続きなら下記の操作は必要有りません

  • モニターを開いて、~/esp/play_mp3_controlフォルダーに移動。
  • 先ずはターゲットの設定を行います。今回はESP32なので、idf.py set-target esp32 を実行。
  • 続いて menuconfig を実行して音源ボードを設定します。モニターで、
    • idf.py menuconfig を実行し、Audio HAL —>  Audio board (ESP32-Lyrat V4.3) —> と進む
    • その画面で ( ) Custom audio board を選択して 保存(s)して下さい。 
    • 設定はここだけです。

次はESP32をPCにつないでモニターから、idf.py -p [USB port] flash monitor と入力して下さい。コンパイルが終了するプログラムが起動します。ボタンを押すと曲が再生されます。

ここに今回のプログラムを保管します。実行は、モニターを開いてターゲット指定から行って下さい。