(5)スピーカーに収納する

今回は以前ネットで購入したスピーカーにWebラジオを収納してみました。先ずは結果から

購入当初は色の落ちた木製のスピーカーでしたが、紙やすりで磨いてステインを塗ったら良い感じに仕上がりました。ラジオの操作部分は正面に有ります。

  • 正面の模様の一部にLCDをプラスチック粘土で埋め込みました。
    • このスペースにAQM1248Aでは大きすぎたのでAQM0802Aに変更しました。
    • AQM0802Aは、8x2(列x行)のキャラクターLCDです。詳細は後で説明します。
  • LCDの下にラジオ局選択用ロータリースイッチを配置しています。
  • 左側にバンド切り替えスイッチ
    • 前回のスイッチが16点。今回が12点と点数が減ったのでオルタネイトスイッチを利用して接点数を増やしています。
    • スイッチがオフで0から11が、オンで12から23が指定されます。
  • 右側のつまみはボリュームです。
  • 実際の実装は下記の様になっています。
  • 電源は5VのACアダプター1個でOKでした。
  • 基板には、ESP32,MAX98357A(I2S amp)とコネクターのみ。
  • LCDはスピーカーの後ろで見えません
  • 箱に対してスピーカーが大きく実装体積は狭いのですが、ラジオ自体の部品が少ないので余裕で収納出来ました。

実装上の問題その1(LCD)

このスピーカーに実装するにあたり、LCDがAQM1248Aではサイズが大き過ぎてスピーカーの小窓に収まりませんでした。そこでAQM1248Aより小型のAQM0802Aを使用しました。AQM082AはインターフェイスがI2CのキャラクターLCDです。詳細はここで説明しています。参照下さい。 このLCDを樹脂粘土でスピーカーに埋め込みました。樹脂粘土は、”パジコ 303116 モデナカラー ブラウン 60g”を使用しました。色がスピーカーと似ていたので使用しました。

実装上の問題その2(ロータリースイッチとオルタネイトスイッチ)

こちらはちょっと厄介でした。そもそも、DIPロータリースイッチ 16ポジション 0~Fは基板実装用で構造的に弱く、大きなツマミを付けて回す事が出来ません。そこで代用品を探したのですが、このスイッチの最大の利点、”4端子で0から15の数字に対応する” ロータリースイッチは見つかりませんでした。

そんな中、比較的近いスイッチがこれです。ー>ロータリースイッチ(1回路12接点)

剛性的には問題無いのですが、問題は以下の2点。

  1.  接点が12個。(前は16個)
  2.  12個の接続を判断するのに12ポート必要。(前は4ポートで16個を判断)

特に”2”は回路への影響が大きい。確かに現在空きのGPIOポートは12個以上有るので、判断にポートを12個使用する事は可能ですが、接続判断のみにポートを12個使うにはもったいない感じがします。

そこで考えたのが、ADCを利用したボタンの判別法です。製品のボードにはADCを用いたボタンが実装されています。ADCボタン用のソフトを利用すれば、1個のGPIOポートで12個の判別が出来ると思ったのですが、だめでした。コンパイルは通るのですが実行すると、”この機能はボタン6個までです” 警告が出て予定の動作をしてくれません。

そもそもこのプログラムは、

  • イベントを発生するエレメントを作成し、リスナーに登録する。
  • audio_event_iface_listen(evt, &msg, portMAX_DELAY); がイベントを監視
  • イベントが発生すると、それに対して動作を起こす。

仕組みになっています。つまり、イベントが発生しないと関数、audio_event_iface_listen(evt, &msg, portMAX_DELAY); でウエイトとなり先に進まないのです。自分でADCを使って12個の状態を判別する関数を作る事は可能ですが、それをイベント登録する術を知りません。(通常の方法でやったらMAX6個までと警告が出る)

そこで考えたのが以下の方法です。

  • 1つのダミーポートをプログラムにエレメント登録する。
  • ADCを使った12個を判別する関数を自作する。
  • スイッチの状態を割り込みを使ってモニタする
  • スイッチの状態が変わるとダミーのポートの状態を変化さえる。

割り込みを使うのはこのプログラム本来の主旨から若干外れる様に思いますが、やってみたら問題無く機能しました。

今回のハード

今回のハードは以下の様になりました。

ESP32 AQM0802A  MAX98357A  ADCポート  バンド切替  ダミーポート 
 GPIO21   SDA
 GPIO22  SCL
 GPIO19   DIN
 GPIO25   LRCLK
 GPIO26   BCLK
 GPIO32   ○
 GPIO36   ○
 GPIO39   ○
  • ADC回路
    • 回路図左側の抵抗の集まりとロータリースイッチがADC回路です。
    • ロータリースイッチの12の接点に12種類の電圧を作成。
    • ロータリースイッチの端子をGPIO32につなぎ、電圧をデジタルにADC変換する。
    • この値を元にロータリースイッチの接続端子を判別するのがADCスイッチの原理です。
  • ダミーポートの追加
    • 前回まで、局選択用に使用していた4ビット(GPIO34,35,36,39)を廃止
    • GPIO39をダミーポートに設定。ここで発生するイベントをプログラムに登録。
  • ロータリースイッチとダミーポートの動作
    • 割り込みを使ってロータリースイッチを監視
    • スイッチの状態が変わるとGPIO33の状態を変更
    • GPIO39とGPIO33を接続されているので、GPIO39の状態も変わる。
    • GPIO39は、イベント登録されているので、audio_event_iface_listen(evt, &msg, portMAX_DELAY);がイベントを認識する。
  • GPIO36のオルタネイトスイッチ
    • 今回のロータリースイッチは12接点なので、接点数を増やす為に使用します。
    • スイッチの状態に合わせてバンド切り替えを行います。スイッチが
      • LOW: 0から11
      • HIGH: 12から23
    • これで24局の選択が可能になります。
    • このポートもプログラムにイベント登録します。スイッチの状態が変わるとイベントが発生し、ラジオ局の設定を行います。
  • AQM0802A(キャラクターLCD)
    • インターフェイスがI2CのキャラクターLCD。
    • デフォルトでSCLはGPIO22となっているようなので前回までI2Sで使用していたGPIO22をこちらに回しました。
  • MAX98357A
    • I2SのSCLをGPIO19に変更しています。
  • MAX98357A出力の先の50kΩのボリューム
    • 音量調整用ボリュームです。
    • 位置的にここがベストとと思えないのですがとりあえずここに付けました。

今回のソフト

プロジェクトの構成。前回 my_boardフォルダーの下に有ったAQM1248フォルダーがAQM0802Aフォルダーに変わっています。


- radiko-esp32-main_04/
      - components/
                   - radiko-esp32/
                              - CMakeLists.txt
                              - Kconfig.projbuild
                              - radiko.c
                              - radiko.h
                              - radiko_jp_rootcert.pem
                   - my_board/
                              - my_board_v1_0/
                                        - board.c
                                        - board.h
                                        - board_def.h
                                        - board_pins_config.c
                              - AQM0802A/
                                        - AQM0802A.c
                                        - AQM0802A.h
                              - CMakeLists.txt
      - main/
                   - CMakeLists.txt
                   - Kconfig.projbuild
                   - play_radiko.c
      - CMakeLists.txt
      - partitions.csv
      - sdkconfig.defaults

以下に主な変更ファイルの説明をします。

  • AQM0802A.c / .h
    • 今回新たに追加されたファイル
    • AQM0802Aの使い方に付いて、AQM0802A for ESP-IDFを参照下さい。
  • board_def.h
    • イベント発生用のエレメントを登録するファイル
    • 前回4ピン定義していた箇所を下記2ピンの登録に変更
    • GPIO36: バンド切替用
    • GPIO39: ダミーポート用
board_def.h

#ifndef _AUDIO_BOARD_DEFINITION_H_
#define _AUDIO_BOARD_DEFINITION_H_

#define BUTTON_CHG_CHAN    GPIO_NUM_39   /* You need to define the GPIO pins of your board */
#define BUTTON_CHG_BAND    GPIO_NUM_36   /* You need to define the GPIO pins of your board */

#define AUDIO_CODEC_DEFAULT_CONFIG(){                   \
        .adc_input  = AUDIO_HAL_ADC_INPUT_LINE1,        \
        .dac_output = AUDIO_HAL_DAC_OUTPUT_ALL,         \
        .codec_mode = AUDIO_HAL_CODEC_MODE_BOTH,        \
        .i2s_iface = {                                  \
            .mode = AUDIO_HAL_MODE_SLAVE,               \
            .fmt = AUDIO_HAL_I2S_NORMAL,                \
            .samples = AUDIO_HAL_48K_SAMPLES,           \
            .bits = AUDIO_HAL_BIT_LENGTH_16BITS,        \
        },                                              \
};

#define INPUT_KEY_NUM     2             /* 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_CHG_CHAN,          \
        .act_id = BUTTON_CHG_CHAN,                      \
    },                                                  \
    {                                                   \
        .type = PERIPH_ID_BUTTON,                       \
        .user_id = INPUT_KEY_USER_ID_CHG_CHAN,          \
        .act_id = BUTTON_CHG_BAND,                      \
    },                                                  \
}

最近気付いたのですが、このファイル今回のプロジェクトで他から参照されていません。でも、ここでピンを定義するとプログラムに反映されます。ちょっとこのファイルの立ち位置が良く分かりません。

次はメインの play_radiko.c の説明です。

play_radiko.c

/* Play M3U HTTP Living stream


   This example code is in the Public Domain (or CC0 licensed, at your option.)
   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "esp_log.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_common.h"
#include "http_stream.h"
#include "i2s_stream.h"
#include "aac_decoder.h"
#include "mp3_decoder.h"
#include "esp_tls.h"
#include "esp_http_client.h"
#include "radiko.h"
#include "esp_peripherals.h"
#include "periph_wifi.h"
#include "board.h"
#include "AQM0802A.h"
#include "esp_adc_cal.h"
#include "driver/timer.h"
#include "driver/gpio.h"

#if __has_include("esp_idf_version.h")
#include "esp_idf_version.h"
#else
#define ESP_IDF_VERSION_VAL(major, minor, patch) 1
#endif

#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
#include "esp_netif.h"
#else
#include "tcpip_adapter.h"
#endif

#define TIMER_DIVIDER         (16)  //  Hardware timer clock divider
#define TIMER_SCALE           (TIMER_BASE_CLK / TIMER_DIVIDER)  // convert counter value to seconds

#define MAX_HTTP_RECV_BUFFER 512
#define MAX_HTTP_content 8192

#define chk_channel     33
#define channel_chg     39
#define chg_band		36

#define _no_data        0
#define _radiko	        1
#define _shtmp3			2
#define _shtaac			3

static const char *TAG = "HTTP_LIVINGSTREAM_EXAMPLE";

#define DEFAULT_VREF    3900        					//Use adc2_vref_to_gpio() to obtain a better estimate
static const adc_atten_t atten = ADC_ATTEN_DB_11;
static const adc_unit_t unit = ADC_UNIT_1;
static const adc_channel_t channel = ADC_CHANNEL_4;     //GPIO32 if ADC1, GPIO14 if ADC2
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
static esp_adc_cal_characteristics_t *adc_chars;

audio_pipeline_handle_t pipeline;
audio_element_handle_t http_stream_reader, i2s_stream_writer, aac_decoder, mp3_decoder;

struct st_info {         //Station info
    int _deco;
    int _st_no;
    char _st_ID[20];
    char _st_URL[200];
};

struct st_info station_info[] = {
	{_radiko, 0, "0", "0"},
	{_radiko, 1, "0", "0"},
	{_radiko, 2, "0", "0"},
	{_radiko, 3, "0", "0"},
	{_radiko, 4, "0", "0"},
	{_radiko, 5, "5", "0"},
	{_radiko, 6, "6", "0"},
	{_radiko, 7, "0", "0"},
	{_radiko, 8, "0", "0"},
	{_radiko, 9, "0", "0"},
	{_radiko, 10, "0", "0"},
	{_radiko, 11, "0", "0"},
	{_radiko, 12, "0", "0"},
	{_radiko, 13, "0", "0"},
	{_radiko, 14, "0", "0"},
	{_radiko, 15, "0", "0"},
	{_no_data, 16, "0", "0"},
	{_no_data, 17, "0", "0"},
	{_shtmp3, 18, "Classic", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=22146"},
	{_shtmp3, 19, "JAZZ1", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=1528122"},
	{_shtmp3, 20, "TOP40", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=1914279"},
	{_shtmp3, 21, "Piano", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=99513978"},
	{_shtmp3, 22, "Dance", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=1817772"},
	{_shtaac, 23, "Antena", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=1796249"}
};

int st_now;

int get_ADC_staion_no()
{
	int data,no,flg;
    int btn_array[13] = {80, 217, 475, 740, 1005, 1285, 1535, 1785, 2055, 2300, 2540, 2800, 3100};
	
    uint32_t adc_reading = 0;
    for (int no = 0; no < 64; no ++)
               adc_reading += adc1_get_raw((adc1_channel_t)ADC_CHANNEL_4);
	
 	data = adc_reading / 64;
	flg = 1; no = 0;
	while(flg)
	{
		if(data < btn_array[no + 1]) flg = 0;
		else no ++;
		
		if(no > 11) { no = 11; flg = 0;	}
	}
	
	if(gpio_get_level(chg_band)) no += 12;
	
	return no;
}

void IRAM_ATTR timer_group_isr_callback(void *args)
{
	if(st_now != get_ADC_staion_no())
	{
		timer_pause(TIMER_GROUP_0, TIMER_0);
		gpio_set_level(chk_channel,!gpio_get_level(channel_chg));
	}

	timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
	timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);

}

void init_port(void)
{
    gpio_config_t io_conf = {};
    io_conf.mode = GPIO_MODE_INPUT;	    			//set as input mode
    io_conf.pin_bit_mask = 0x9ULL << 36;			//bit mask of the pins GPIO36,39	
    io_conf.pull_up_en = 0;						    //disable pull-up mode
    gpio_config(&io_conf);
    
    io_conf.intr_type = GPIO_INTR_DISABLE;			//disable interrupt
    io_conf.mode = GPIO_MODE_OUTPUT;				//set as input mode
    io_conf.pin_bit_mask = 1ULL << chk_channel;		//bit mask of the pins GPIO36,39
    gpio_config(&io_conf);
	gpio_set_level(chk_channel, 1);					//set high
	
    adc1_config_width(width);
    adc1_config_channel_atten(channel, atten);
    adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
  	esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_chars);

	timer_config_t config = {
        .divider = TIMER_DIVIDER,
        .counter_dir = TIMER_COUNT_UP,
        .counter_en = TIMER_PAUSE,
        .alarm_en = TIMER_ALARM_EN,
        .auto_reload = true,
	};

    timer_init(TIMER_GROUP_0, TIMER_0, &config);					// For the timer counter to a initial value
    timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);				// Set alarm value and enable alarm interrupt
    timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_SCALE / 2);	// 0.5 sec
	timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_group_isr_callback, (void *) TIMER_0, ESP_INTR_FLAG_IRAM, NULL);
}

int _http_stream_event_handle(http_stream_event_msg_t *msg)
{
    if (msg->event_id == HTTP_STREAM_RESOLVE_ALL_TRACKS) {
        return ESP_OK;
    }

    if (msg->event_id == HTTP_STREAM_FINISH_TRACK) {
        return http_stream_next_track(msg->el);
    }
    if (msg->event_id == HTTP_STREAM_FINISH_PLAYLIST) {
        return http_stream_fetch_again(msg->el);
    }
    return ESP_OK;
}

void print_station_info(int st_no)
{
	char c_buf[30];
	
	AQM0802A_clear();
	sprintf(&c_buf[10], "%02d", st_no);
	strcpy(c_buf,"Ch ");
	strcat(c_buf,&c_buf[10]);
	AQM0802A_setCursor(1, 0);
	AQM0802A_print(c_buf);
	
	switch(station_info[st_no]._deco)
    {
        case _no_data:  strcpy(c_buf, "No_Data");
			    		break;
			    		
		case _radiko: 	strcpy(c_buf, (stations + st_no)->id);
			    		break;
		case _shtmp3: 	
		case _shtaac: 	strcpy(c_buf, station_info[st_no]._st_ID);
						break;
						
	}			
			    
	st_no = strlen(c_buf);
	if(st_no > 8) {  c_buf[8] = 0; st_no = 8; }
	st_no = 8 - st_no;
	st_no /= 2;  
	AQM0802A_setCursor(st_no, 1);
	AQM0802A_print(c_buf);
	
}

void app_main(void)
{
	int flg_no;

    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES) 
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
    ESP_ERROR_CHECK(esp_netif_init());
#else
    tcpip_adapter_init();
#endif

	init_port();
	i2c_master_init();
	AQM0802A_init();

    esp_log_level_set("*", ESP_LOG_INFO);
    esp_log_level_set(TAG, ESP_LOG_DEBUG);

    ESP_LOGI(TAG, "[ 3 ] Start and wait for Wi-Fi network");
    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);

    AQM0802A_clear();
	AQM0802A_setCursor(0, 0);
	AQM0802A_print("Connect");
	AQM0802A_setCursor(2, 1);
	AQM0802A_print("WiFi");
	
    periph_wifi_cfg_t wifi_cfg = 
    {
        .ssid = CONFIG_WIFI_SSID,
        .password = CONFIG_WIFI_PASSWORD,
    };

    esp_periph_handle_t wifi_handle = periph_wifi_init(&wifi_cfg);
    esp_periph_start(set, wifi_handle);
    periph_wifi_wait_for_connected(wifi_handle, portMAX_DELAY);

//------------------------------------------------------------------------------------
    AQM0802A_clear();
	AQM0802A_setCursor(1, 0);
	AQM0802A_print("Create");
	AQM0802A_setCursor(0, 1);
	AQM0802A_print("pipeline");
    ESP_LOGI(TAG, "[2.0] Create audio pipeline for playback");
    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    pipeline = audio_pipeline_init(&pipeline_cfg);

    ESP_LOGI(TAG, "[2.1] Create http stream to read data");
    http_stream_cfg_t http_cfg = HTTP_STREAM_CFG_DEFAULT();
    http_cfg.event_handle = _http_stream_event_handle;
    http_cfg.type = AUDIO_STREAM_READER;
    http_cfg.enable_playlist_parser = true;
    http_stream_reader = http_stream_init(&http_cfg);

    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] Create aac decoder to decode aac file");
    aac_decoder_cfg_t aac_cfg = DEFAULT_AAC_DECODER_CONFIG();
    aac_decoder = aac_decoder_init(&aac_cfg);

    ESP_LOGI(TAG, "[2.3M] Create mp3 decoder to decode mp3 file");
    mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
    mp3_decoder = mp3_decoder_init(&mp3_cfg);

    const char *m_link_tag[3] = {"http", "mp3", "i2s"};
   	const char *link_tag[3] = {"http", "aac", "i2s"};

   	ESP_LOGI(TAG, "[2.4] Register all elements to audio pipeline");
   	audio_pipeline_register(pipeline, http_stream_reader, "http");
   	audio_pipeline_register(pipeline, aac_decoder,        "aac");
   	audio_pipeline_register(pipeline, i2s_stream_writer,  "i2s");
   	audio_pipeline_register(pipeline, mp3_decoder,        "mp3");

	st_now = get_ADC_staion_no();
	
    switch(station_info[st_now]._deco)
    {
    	case _radiko:	
    	case _shtaac:	ESP_LOGI(TAG, "[2.5] Link it together http_stream-->aac_decoder-->i2s_stream-->[codec_chip]");
    					audio_pipeline_link(pipeline, &link_tag[0], 3);
    					break;
    					
    	case _shtmp3: 	ESP_LOGI(TAG, "[2.5M] Link it together http_stream-->mp3_decoder-->i2s_stream-->[codec_chip]");
     					audio_pipeline_link(pipeline, &m_link_tag[0], 3);
     					break;
	}
	
    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.1] Listening event from all elements of pipeline");
    audio_pipeline_set_listener(pipeline, evt);

    ESP_LOGI(TAG, "[4.2] Listening event from peripherals");
    audio_event_iface_set_listener(esp_periph_set_get_event_iface(set), evt);
    
//------------------------------------------------------------------------------------
    AQM0802A_clear();
	AQM0802A_setCursor(1, 0);
	AQM0802A_print("Setup");
	AQM0802A_setCursor(1, 1);
	AQM0802A_print("Radiko");
  	auth();
    get_station_list();

    switch(station_info[st_now]._deco)
    {
        case _radiko:   ESP_LOGI(TAG, "[2.6] Set up radiko uri)");							
		                generate_playlist_url(stations + st_now);
		                audio_element_set_uri(http_stream_reader, _playlist_url);
		                break;
		                
	    case _shtmp3:
	    case _shtaac:   ESP_LOGI(TAG, "[2.6M] Set up shoutcast uri");
		                audio_element_set_uri(http_stream_reader, station_info[st_now]._st_URL);
		                break;
	}

	ESP_LOGI(TAG, "[ 5 ] Start audio_pipeline");
	if(station_info[st_now]._deco) 	
	    audio_pipeline_run(pipeline);

	print_station_info(st_now);

    timer_start(TIMER_GROUP_0, TIMER_0);				// Start timer

    while (1) 
    {
        audio_event_iface_msg_t msg;
        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
        if (ret != ESP_OK) 
        {
            ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
            continue;
        }

	    if(msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO)
	    {
	        audio_element_info_t music_info = {0};
		    switch(station_info[st_now]._deco)
    		{
		        case _radiko:	 
		        case _shtaac:   
    	    				    audio_element_getinfo(aac_decoder, &music_info);
    	    				    ESP_LOGI(TAG, "[ * ] Receive music info from aac decoder, sample_rates=%d, bits=%d, ch=%d",
    	    				         music_info.sample_rates, music_info.bits, music_info.channels);
 					    	    break;
			    	    
				case _shtmp3:   audio_element_getinfo(mp3_decoder, &music_info);
		    				    ESP_LOGI(TAG, "[ * ] Receive music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d",
		    				             music_info.sample_rates, music_info.bits, music_info.channels);
		    				    break;
			}
		    audio_element_setinfo(i2s_stream_writer, &music_info);
		    i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
		}
        
   		if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) http_stream_reader
		            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS && (int) msg.data == AEL_STATUS_ERROR_OPEN) 		
        {
	        ESP_LOGW(TAG, "[ * ] Restart stream");
	        audio_pipeline_stop(pipeline);
	        audio_pipeline_wait_for_stop(pipeline);

	        if(station_info[st_now]._deco == _shtmp3)
		        audio_element_reset_state(mp3_decoder);
			else
    	        audio_element_reset_state(aac_decoder);
			
			audio_element_reset_state(i2s_stream_writer);
	        audio_pipeline_reset_ringbuffer(pipeline);
	        audio_pipeline_reset_items_state(pipeline);
	        audio_pipeline_run(pipeline);
	        continue;
	    }
	    
        if (msg.source_type == PERIPH_ID_BUTTON)
        {
			timer_pause(TIMER_GROUP_0, TIMER_0);
        	vTaskDelay(1000 / portTICK_PERIOD_MS);
	        flg_no = get_ADC_staion_no();

    	    if( flg_no != st_now)
    	    {
  	        	if(station_info[st_now]._deco)
  	        	{
		        	audio_pipeline_stop(pipeline);
   			        audio_pipeline_wait_for_stop(pipeline);
					audio_pipeline_terminate(pipeline);
   					audio_pipeline_unlink(pipeline);
    				audio_pipeline_remove_listener(pipeline);
				}
    	   		st_now = flg_no;
  	        	ESP_LOGI(TAG, "Station No:%d",flg_no);
				
			    switch(station_info[st_now]._deco)
		    	{
		    		case _shtmp3:	ESP_LOGI(TAG, "[2.5M] Link it together http_stream-->mp3_decoder-->i2s_stream-->[codec_chip]");
						    		audio_pipeline_link(pipeline, &m_link_tag[0], 3);
    						        audio_element_set_uri(http_stream_reader, station_info[st_now]._st_URL);
						            break;
				            
					case _shtaac: 	
					case _radiko: 	ESP_LOGI(TAG, "[2.5] Link it together http_stream-->aac_decoder-->i2s_stream-->[codec_chip]");
						    		audio_pipeline_link(pipeline, &link_tag[0], 3);
			
									if(station_info[st_now]._deco == _radiko)
									{
										generate_playlist_url(stations + st_now);
		    							audio_element_set_uri(http_stream_reader, _playlist_url);
		    						}	
									else audio_element_set_uri(http_stream_reader, station_info[st_now]._st_URL);
						            break;
		       	}
    			ESP_LOGI(TAG, "[4.1] Listening event from all elements of pipeline");
    			audio_pipeline_set_listener(pipeline, evt);
  	        	if(station_info[st_now]._deco)
					audio_pipeline_run(pipeline);
			   	print_station_info(st_now);
        	}
    		//Start timer interrupt
			timer_start(TIMER_GROUP_0, TIMER_0);
  		}
    }
    
    ESP_LOGI(TAG, "[ 6 ] Stop audio_pipeline");
    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_terminate(pipeline);

    audio_pipeline_unregister(pipeline, http_stream_reader);
    audio_pipeline_unregister(pipeline, i2s_stream_writer);
    audio_pipeline_unregister(pipeline, aac_decoder);

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

    /* Stop all peripherals before removing the listener */
    esp_periph_set_stop_all(set);
    audio_event_iface_remove_listener(esp_periph_set_get_event_iface(set), evt);

    /* Make sure audio_pipeline_remove_listener & audio_event_iface_remove_listener are called before destroying event_iface */
    audio_event_iface_destroy(evt);

    /* Release all resources */
    audio_pipeline_deinit(pipeline);
    audio_element_deinit(http_stream_reader);
    audio_element_deinit(i2s_stream_writer);
    audio_element_deinit(aac_decoder);
    esp_periph_set_destroy(set);
}

  • プログラムは232行のvoid app_main(void)から始まります。
  • プログラムの進行に合わせて、AQM0802A_print();関数を使ってLCDに状況を表示しています。
  • 317行:st_now = get_ADC_staion_no();
    • get_ADC_staion_no();関数は、ロータリースイッチの接点を読み込む関数です。
    • 関数の定義は114行から行っています。
    • 電圧を64回測定しその平均を取っています。
      • 当初1,2回の測定でしたが測定値がばらついてしまいました。
      • そこで測定回数を増やして行った所64回位でかなり安定しました。
    • 測定した値を元にロータリースイッチの接点を判別します。
      • この時点で接点の値は0から11となります。
    • オルタネートスイッチでバンド切替
      • スイッチがオフで有れば、ロータリースイッチの値は0から11。
      • オンで有れば更に12が足して 12から23。
      • スイッチのオンオフとロータリースイッチの値を合わせて、接点が0から23となります。
    • ADCの詳細は、ESP-IDFでADCを使う を参照下さい。
  • プログラムはStreamingの準備を行い、再生ループに入る直前で(169行)今回の目玉の1つ。ロータリースイッチ監視用の割り込みを開始します。
    • タイマーの設定は、178行から行っています。これで割り込みの間隔が0.5秒になります。
    • 割り込みがかかると、138行のvoid IRAM_ATTR timer_group_isr_callback(void *args)が実行されます。
      • ロータリースイッチの値を読み、値が前回と違う場合GPIO39の値を変更します。
      • この値が変わると、audio_event_iface_listen(evt, &msg, portMAX_DELAY);関数にイベントが送られます。
  • プログラムは、再生しながらロータリースイッチを監視する割り込み処理を行い、値が変わればGPIO39の状態を変化させてイベントを発生させるを繰り返します。
  • その他、
    • 局データ保存用構造体のデコード方式の指定に、0:指定無しを追加
      • 局データ保存用構造体のデコード方式の指定 int _deco; に新たに 0:指定無しを追加しました。これにより、 int _deco;は下記の4種類になりました
        • 0:指定無し
        • 1:Radiko
        • 2:MP3
        • 3:AAC
      • 局の保存を行っていない構造体に、”指定無し”を設定します。
      • 局の保存を行っていない番号を指定するとLCDに、”No_Data” と表示されます。
    • ADCの値が計算値と測定値が若干違う
      • ロータリースイッチの接点電圧から計算した値と実際の測定値に若干の違いが有りました。
        • 下の表を参照下さい。
        • 計算値、
          • 今回トータル電圧として3.3Vを供給
          • 接点が0の場合、3.3VとGNDの間に30kと2kの抵抗が有ので
          • この時の電圧は V=3.3/((30+2)*1000)*2。
          • ADCの分解能を2^12としているので、3.3VがADCで2^12になるとすると上記のVはADC後に V/3.3*2^12となる。
          • これが計算値です。
        • 測定値
          • 実際にADC変換して得た測定値です。
          • 64回測定し平均しています。
      • ロータリースイッチの接点を判別する時にこの値を使う(プログラムでは117行の配列で使用)のですが、測定値が計算値と合わないのは疑問
      • 計算値と測定値が合わなかったのですが、測定値が安定していたので今回は測定値を接点の判断をしています。
    • ADCボタンの判別方法の追加説明
      • play_radiko.cの114行:int get_ADC_staion_no() が判別関数
      • 各接点での測定値を挟む様に配列を宣言します。
      • 117行:int btn_array[13] = {80, 217, 475, 740, 1005, 1285, 1535, 1785, 2055, 2300, 2540, 2800, 3100}; がこれに当たります。
      • この配列の要素ですが、接点0の測定値は下の表から105。配列の0番目と1番目の要素はその値を挟む様に80,217と設定しています。この様に各要素を設定します。
      • 次に接点の値を64回測定し平均します。測定値安定の為。
      • この値が配列の何番目と何番目に有るかチェックします。
      • この様にしてロータリースイッチの接点を判断しています。
 スイッチ  抵抗  計算値  測定値 
11223038.52930
1014.72791.52670
9102550.32410
89.42327.92190
76.72051.41920
64.71794.91650
54.71572.81420
441303.31150
331024.0860
23774.9620
12481.9330
02256.0105
ADC変換に関する計算値と測定値

コンパイルと実行

  • ESP32を使用することを前提としています。
  • 先ずは、idf.py menuconfig を実行して初期設定を行います。
    • Topー>Audio HALー>Audio board custom audio board を選択
    • Example ConfigurationでWiFi関係の設定を行います。mainフォルダー下のKconfig.projbuildでWiFi関係の設定を行っていれば入力不要。
    • Radiko Configurationで認証キーを入力します。今回は、radiko-esp32フォルダー下のKconfig.projbuildで指定しているので入力不要。
  • この後、idf.py -p [USB Port] flash monitor を実行して下さい。コンパイルして実行出来ます。
  • 実行後LCDに実行経過を表示します。しばらくしてLCDに局データが表示されラジオの再生が始まります。
  • ロータリースイッチを回して下さい。ラジオ局が選択され局表示も更新されます。
  • ラジオ局はオルタネートスイッチがオフで、0から11が。オンで12から23が選択出来ます。

次は

電源を入れるだけでラジオが再生され、ラジオ局も選択出来るので非常に気に入っています。それに何も気にせず電源を落せるのも便利です。今回でWebRadioとして最終レベルと思っています。これにさらに機能を追加するとすると、コンパイル無しで WiFiの設定、(IDとPassword指定)。局データの書き換えを行う。位でしょうか。